Meilenstein-Präfixe aus Klassennamen entfernt
This commit is contained in:
68
java-packages-inventory.txt
Normal file
68
java-packages-inventory.txt
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
pdf-umbenenner-adapter-in-cli/src/main/java/de/gecheckt/pdf/umbenenner/adapter/inbound/cli/package-info.java | de.gecheckt.pdf.umbenenner.adapter.inbound.cli | |
|
||||||
|
pdf-umbenenner-adapter-in-cli/src/main/java/de/gecheckt/pdf/umbenenner/adapter/inbound/cli/SchedulerBatchCommand.java | de.gecheckt.pdf.umbenenner.adapter.inbound.cli | class | SchedulerBatchCommand
|
||||||
|
pdf-umbenenner-adapter-in-cli/src/test/java/de/gecheckt/pdf/umbenenner/adapter/inbound/cli/SchedulerBatchCommandTest.java | de.gecheckt.pdf.umbenenner.adapter.inbound.cli | class | SchedulerBatchCommandTest
|
||||||
|
pdf-umbenenner-adapter-out/src/main/java/de/gecheckt/pdf/umbenenner/adapter/outbound/configuration/package-info.java | de.gecheckt.pdf.umbenenner.adapter.outbound.configuration | |
|
||||||
|
pdf-umbenenner-adapter-out/src/main/java/de/gecheckt/pdf/umbenenner/adapter/outbound/configuration/PropertiesConfigurationPortAdapter.java | de.gecheckt.pdf.umbenenner.adapter.outbound.configuration | class | PropertiesConfigurationPortAdapter
|
||||||
|
pdf-umbenenner-adapter-out/src/main/java/de/gecheckt/pdf/umbenenner/adapter/outbound/lock/FilesystemRunLockPortAdapter.java | de.gecheckt.pdf.umbenenner.adapter.outbound.lock | class | FilesystemRunLockPortAdapter
|
||||||
|
pdf-umbenenner-adapter-out/src/main/java/de/gecheckt/pdf/umbenenner/adapter/outbound/lock/package-info.java | de.gecheckt.pdf.umbenenner.adapter.outbound.lock | |
|
||||||
|
pdf-umbenenner-adapter-out/src/main/java/de/gecheckt/pdf/umbenenner/adapter/outbound/package-info.java | de.gecheckt.pdf.umbenenner.adapter.outbound | |
|
||||||
|
pdf-umbenenner-adapter-out/src/main/java/de/gecheckt/pdf/umbenenner/adapter/outbound/pdfextraction/package-info.java | de.gecheckt.pdf.umbenenner.adapter.outbound.pdfextraction | |
|
||||||
|
pdf-umbenenner-adapter-out/src/main/java/de/gecheckt/pdf/umbenenner/adapter/outbound/pdfextraction/PdfTextExtractionPortAdapter.java | de.gecheckt.pdf.umbenenner.adapter.outbound.pdfextraction | class | PdfTextExtractionPortAdapter
|
||||||
|
pdf-umbenenner-adapter-out/src/main/java/de/gecheckt/pdf/umbenenner/adapter/outbound/sourcedocument/package-info.java | de.gecheckt.pdf.umbenenner.adapter.outbound.sourcedocument | |
|
||||||
|
pdf-umbenenner-adapter-out/src/main/java/de/gecheckt/pdf/umbenenner/adapter/outbound/sourcedocument/SourceDocumentCandidatesPortAdapter.java | de.gecheckt.pdf.umbenenner.adapter.outbound.sourcedocument | class | SourceDocumentCandidatesPortAdapter
|
||||||
|
pdf-umbenenner-adapter-out/src/test/java/de/gecheckt/pdf/umbenenner/adapter/outbound/configuration/PropertiesConfigurationPortAdapterTest.java | de.gecheckt.pdf.umbenenner.adapter.outbound.configuration | class | PropertiesConfigurationPortAdapterTest
|
||||||
|
pdf-umbenenner-adapter-out/src/test/java/de/gecheckt/pdf/umbenenner/adapter/outbound/lock/FilesystemRunLockPortAdapterTest.java | de.gecheckt.pdf.umbenenner.adapter.outbound.lock | class | FilesystemRunLockPortAdapterTest
|
||||||
|
pdf-umbenenner-adapter-out/src/test/java/de/gecheckt/pdf/umbenenner/adapter/outbound/pdfextraction/PdfTextExtractionPortAdapterTest.java | de.gecheckt.pdf.umbenenner.adapter.outbound.pdfextraction | class | PdfTextExtractionPortAdapterTest
|
||||||
|
pdf-umbenenner-adapter-out/src/test/java/de/gecheckt/pdf/umbenenner/adapter/outbound/sourcedocument/SourceDocumentCandidatesPortAdapterTest.java | de.gecheckt.pdf.umbenenner.adapter.outbound.sourcedocument | class | SourceDocumentCandidatesPortAdapterTest
|
||||||
|
pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/config/InvalidStartConfigurationException.java | de.gecheckt.pdf.umbenenner.application.config | class | InvalidStartConfigurationException
|
||||||
|
pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/config/package-info.java | de.gecheckt.pdf.umbenenner.application.config | |
|
||||||
|
pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/config/StartConfiguration.java | de.gecheckt.pdf.umbenenner.application.config | record | StartConfiguration
|
||||||
|
pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/config/StartConfigurationValidator.java | de.gecheckt.pdf.umbenenner.application.config | class | StartConfigurationValidator
|
||||||
|
pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/package-info.java | de.gecheckt.pdf.umbenenner.application | |
|
||||||
|
pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/in/BatchRunOutcome.java | de.gecheckt.pdf.umbenenner.application.port.in | enum | BatchRunOutcome
|
||||||
|
pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/in/package-info.java | de.gecheckt.pdf.umbenenner.application.port.in | |
|
||||||
|
pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/in/RunBatchProcessingUseCase.java | de.gecheckt.pdf.umbenenner.application.port.in | interface | RunBatchProcessingUseCase
|
||||||
|
pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/ClockPort.java | de.gecheckt.pdf.umbenenner.application.port.out | interface | ClockPort
|
||||||
|
pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/ConfigurationPort.java | de.gecheckt.pdf.umbenenner.application.port.out | interface | ConfigurationPort
|
||||||
|
pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/package-info.java | de.gecheckt.pdf.umbenenner.application.port.out | |
|
||||||
|
pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/PdfTextExtractionPort.java | de.gecheckt.pdf.umbenenner.application.port.out | interface | PdfTextExtractionPort
|
||||||
|
pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/RunLockPort.java | de.gecheckt.pdf.umbenenner.application.port.out | interface | RunLockPort
|
||||||
|
pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/RunLockUnavailableException.java | de.gecheckt.pdf.umbenenner.application.port.out | class | RunLockUnavailableException
|
||||||
|
pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/SourceDocumentAccessException.java | de.gecheckt.pdf.umbenenner.application.port.out | |
|
||||||
|
pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/port/out/SourceDocumentCandidatesPort.java | de.gecheckt.pdf.umbenenner.application.port.out | interface | SourceDocumentCandidatesPort
|
||||||
|
pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/service/M3DocumentProcessingService.java | de.gecheckt.pdf.umbenenner.application.service | class | M3DocumentProcessingService
|
||||||
|
pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/service/M3PreCheckEvaluator.java | de.gecheckt.pdf.umbenenner.application.service | class | M3PreCheckEvaluator
|
||||||
|
pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/service/package-info.java | de.gecheckt.pdf.umbenenner.application.service | |
|
||||||
|
pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/usecase/M2BatchRunProcessingUseCase.java | de.gecheckt.pdf.umbenenner.application.usecase | class | M2BatchRunProcessingUseCase
|
||||||
|
pdf-umbenenner-application/src/main/java/de/gecheckt/pdf/umbenenner/application/usecase/package-info.java | de.gecheckt.pdf.umbenenner.application.usecase | |
|
||||||
|
pdf-umbenenner-application/src/test/java/de/gecheckt/pdf/umbenenner/application/config/StartConfigurationValidatorTest.java | de.gecheckt.pdf.umbenenner.application.config | class | StartConfigurationValidatorTest
|
||||||
|
pdf-umbenenner-application/src/test/java/de/gecheckt/pdf/umbenenner/application/service/M3DocumentProcessingServiceTest.java | de.gecheckt.pdf.umbenenner.application.service | class | M3DocumentProcessingServiceTest
|
||||||
|
pdf-umbenenner-application/src/test/java/de/gecheckt/pdf/umbenenner/application/service/M3PreCheckEvaluatorTest.java | de.gecheckt.pdf.umbenenner.application.service | class | M3PreCheckEvaluatorTest
|
||||||
|
pdf-umbenenner-application/src/test/java/de/gecheckt/pdf/umbenenner/application/usecase/M2BatchRunProcessingUseCaseTest.java | de.gecheckt.pdf.umbenenner.application.usecase | class | M2BatchRunProcessingUseCaseTest
|
||||||
|
pdf-umbenenner-bootstrap/src/main/java/de/gecheckt/pdf/umbenenner/bootstrap/BootstrapRunner.java | de.gecheckt.pdf.umbenenner.bootstrap | class | BootstrapRunner
|
||||||
|
pdf-umbenenner-bootstrap/src/main/java/de/gecheckt/pdf/umbenenner/bootstrap/package-info.java | de.gecheckt.pdf.umbenenner.bootstrap | |
|
||||||
|
pdf-umbenenner-bootstrap/src/main/java/de/gecheckt/pdf/umbenenner/bootstrap/PdfUmbenennerApplication.java | de.gecheckt.pdf.umbenenner.bootstrap | class | PdfUmbenennerApplication
|
||||||
|
pdf-umbenenner-bootstrap/src/test/java/de/gecheckt/pdf/umbenenner/bootstrap/BootstrapRunnerTest.java | de.gecheckt.pdf.umbenenner.bootstrap | class | BootstrapRunnerTest
|
||||||
|
pdf-umbenenner-bootstrap/src/test/java/de/gecheckt/pdf/umbenenner/bootstrap/ExecutableJarSmokeTestIT.java | de.gecheckt.pdf.umbenenner.bootstrap | class | ExecutableJarSmokeTestIT
|
||||||
|
pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/BatchRunContext.java | de.gecheckt.pdf.umbenenner.domain.model | |
|
||||||
|
pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/M3DocumentProcessingOutcome.java | de.gecheckt.pdf.umbenenner.domain.model | |
|
||||||
|
pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/M3PreCheckFailed.java | de.gecheckt.pdf.umbenenner.domain.model | record | M3PreCheckFailed
|
||||||
|
pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/M3PreCheckFailureReason.java | de.gecheckt.pdf.umbenenner.domain.model | enum | M3PreCheckFailureReason
|
||||||
|
pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/M3PreCheckPassed.java | de.gecheckt.pdf.umbenenner.domain.model | record | M3PreCheckPassed
|
||||||
|
pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/M3ProcessingDecision.java | de.gecheckt.pdf.umbenenner.domain.model | |
|
||||||
|
pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/M3TechnicalDocumentError.java | de.gecheckt.pdf.umbenenner.domain.model | record | M3TechnicalDocumentError
|
||||||
|
pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/package-info.java | de.gecheckt.pdf.umbenenner.domain.model | |
|
||||||
|
pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/PdfExtractionContentError.java | de.gecheckt.pdf.umbenenner.domain.model | record | PdfExtractionContentError
|
||||||
|
pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/PdfExtractionResult.java | de.gecheckt.pdf.umbenenner.domain.model | |
|
||||||
|
pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/PdfExtractionSuccess.java | de.gecheckt.pdf.umbenenner.domain.model | record | PdfExtractionSuccess
|
||||||
|
pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/PdfExtractionTechnicalError.java | de.gecheckt.pdf.umbenenner.domain.model | record | PdfExtractionTechnicalError
|
||||||
|
pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/PdfPageCount.java | de.gecheckt.pdf.umbenenner.domain.model | record | PdfPageCount
|
||||||
|
pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/ProcessingStatus.java | de.gecheckt.pdf.umbenenner.domain.model | enum | ProcessingStatus
|
||||||
|
pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/RunId.java | de.gecheckt.pdf.umbenenner.domain.model | |
|
||||||
|
pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/SourceDocumentCandidate.java | de.gecheckt.pdf.umbenenner.domain.model | record | SourceDocumentCandidate
|
||||||
|
pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/model/SourceDocumentLocator.java | de.gecheckt.pdf.umbenenner.domain.model | record | SourceDocumentLocator
|
||||||
|
pdf-umbenenner-domain/src/main/java/de/gecheckt/pdf/umbenenner/domain/package-info.java | de.gecheckt.pdf.umbenenner.domain | |
|
||||||
|
pdf-umbenenner-domain/src/test/java/de/gecheckt/pdf/umbenenner/domain/model/BatchRunContextTest.java | de.gecheckt.pdf.umbenenner.domain.model | class | BatchRunContextTest
|
||||||
|
pdf-umbenenner-domain/src/test/java/de/gecheckt/pdf/umbenenner/domain/model/M3DocumentProcessingOutcomeTest.java | de.gecheckt.pdf.umbenenner.domain.model | class | M3DocumentProcessingOutcomeTest
|
||||||
|
pdf-umbenenner-domain/src/test/java/de/gecheckt/pdf/umbenenner/domain/model/ProcessingStatusTest.java | de.gecheckt.pdf.umbenenner.domain.model | class | ProcessingStatusTest
|
||||||
|
pdf-umbenenner-domain/src/test/java/de/gecheckt/pdf/umbenenner/domain/model/RunIdTest.java | de.gecheckt.pdf.umbenenner.domain.model | class | RunIdTest
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.application.service;
|
package de.gecheckt.pdf.umbenenner.application.service;
|
||||||
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
|
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.M3DocumentProcessingOutcome;
|
import de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.M3TechnicalDocumentError;
|
import de.gecheckt.pdf.umbenenner.domain.model.TechnicalDocumentError;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionContentError;
|
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionContentError;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionResult;
|
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionResult;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess;
|
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess;
|
||||||
@@ -12,45 +12,43 @@ import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Orchestrates M3 document processing pipeline: extraction → pre-checks → outcome classification.
|
* Orchestrates document processing pipeline: extraction → pre-checks → outcome classification.
|
||||||
* <p>
|
* <p>
|
||||||
* Converts technical extraction results into M3 processing outcomes through this pipeline:
|
* Converts technical extraction results into processing outcomes through this pipeline:
|
||||||
* <ol>
|
* <ol>
|
||||||
* <li>If extraction fails (content or technical): {@link M3TechnicalDocumentError}</li>
|
* <li>If extraction fails (content or technical): {@link TechnicalDocumentError}</li>
|
||||||
* <li>If extraction succeeds: Evaluate M3 pre-checks via {@link M3PreCheckEvaluator}</li>
|
* <li>If extraction succeeds: Evaluate pre-checks via {@link PreCheckEvaluator}</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
* <p>
|
* <p>
|
||||||
* This service produces {@link M3DocumentProcessingOutcome}, a sealed interface that covers
|
* This service produces {@link DocumentProcessingOutcome}, a sealed interface that covers
|
||||||
* all four M3 document processing outcomes:
|
* all document processing outcomes:
|
||||||
* <ul>
|
* <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>Pre-check failed (deterministic content error: no usable text, page limit exceeded)</li>
|
||||||
* <li>Technical document error (I/O, access, PDF parsing, etc.)</li>
|
* <li>Technical document error (I/O, access, PDF parsing, etc.)</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* <p>
|
* <p>
|
||||||
* This service is stateless and thread-safe.
|
* 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>
|
* <p>
|
||||||
* Pipeline:
|
* Pipeline:
|
||||||
* <ol>
|
* <ol>
|
||||||
* <li>Extract text and page count from the PDF candidate</li>
|
* <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 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>
|
* </ol>
|
||||||
*
|
*
|
||||||
* @param candidate the document candidate to process
|
* @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 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)
|
* @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
|
* @throws NullPointerException if any parameter is null
|
||||||
*/
|
*/
|
||||||
public static M3DocumentProcessingOutcome processDocument(
|
public static DocumentProcessingOutcome processDocument(
|
||||||
SourceDocumentCandidate candidate,
|
SourceDocumentCandidate candidate,
|
||||||
PdfExtractionResult extractionResult,
|
PdfExtractionResult extractionResult,
|
||||||
StartConfiguration configuration) {
|
StartConfiguration configuration) {
|
||||||
@@ -61,20 +59,20 @@ public class M3DocumentProcessingService {
|
|||||||
|
|
||||||
return switch (extractionResult) {
|
return switch (extractionResult) {
|
||||||
case PdfExtractionSuccess success ->
|
case PdfExtractionSuccess success ->
|
||||||
// Extraction succeeded: evaluate M3 pre-checks
|
// Extraction succeeded: evaluate pre-checks
|
||||||
M3PreCheckEvaluator.evaluate(candidate, success, configuration);
|
PreCheckEvaluator.evaluate(candidate, success, configuration);
|
||||||
|
|
||||||
case PdfExtractionContentError contentError ->
|
case PdfExtractionContentError contentError ->
|
||||||
// PDF content not extractable: classify as technical document error
|
// 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 ->
|
case PdfExtractionTechnicalError technicalError ->
|
||||||
// Technical failure during extraction: potentially retryable
|
// 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
|
// Static utility class – no instances
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,36 +1,34 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.application.service;
|
package de.gecheckt.pdf.umbenenner.application.service;
|
||||||
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
|
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.M3DocumentProcessingOutcome;
|
import de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckFailureReason;
|
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailureReason;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckFailed;
|
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailed;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckPassed;
|
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckPassed;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess;
|
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
|
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluates whether a successfully extracted PDF passes M3 pre-checks.
|
* Evaluates whether a successfully extracted PDF passes pre-checks.
|
||||||
* <p>
|
* <p>
|
||||||
* M3 Pre-checks verify that:
|
* Pre-checks verify that:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>The extracted text contains at least one meaningful character after normalization</li>
|
* <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>
|
* <li>The document's page count does not exceed the configured limit</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* <p>
|
* <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
|
* 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.
|
* and will not proceed further in the current batch run.
|
||||||
* <p>
|
* <p>
|
||||||
* This service is stateless and thread-safe.
|
* 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>
|
* <p>
|
||||||
* Pre-check logic:
|
* Pre-check logic:
|
||||||
* <ol>
|
* <ol>
|
||||||
@@ -38,19 +36,19 @@ public class M3PreCheckEvaluator {
|
|||||||
* <li>Check if document page count does not exceed the configured limit</li>
|
* <li>Check if document page count does not exceed the configured limit</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
* <p>
|
* <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.
|
* with a specific reason if any check fails.
|
||||||
* <p>
|
* <p>
|
||||||
* Note: Returns {@link M3DocumentProcessingOutcome} to integrate cleanly with the complete
|
* Note: Returns {@link DocumentProcessingOutcome} to integrate cleanly with the complete
|
||||||
* M3 document processing pipeline.
|
* document processing pipeline.
|
||||||
*
|
*
|
||||||
* @param candidate the source document metadata
|
* @param candidate the source document metadata
|
||||||
* @param extraction the successfully extracted PDF content
|
* @param extraction the successfully extracted PDF content
|
||||||
* @param configuration the startup configuration (used for maxPages limit)
|
* @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
|
* @throws NullPointerException if any parameter is null
|
||||||
*/
|
*/
|
||||||
public static M3DocumentProcessingOutcome evaluate(
|
public static DocumentProcessingOutcome evaluate(
|
||||||
SourceDocumentCandidate candidate,
|
SourceDocumentCandidate candidate,
|
||||||
PdfExtractionSuccess extraction,
|
PdfExtractionSuccess extraction,
|
||||||
StartConfiguration configuration) {
|
StartConfiguration configuration) {
|
||||||
@@ -61,28 +59,28 @@ public class M3PreCheckEvaluator {
|
|||||||
|
|
||||||
// Pre-check 1: Verify document has usable text
|
// Pre-check 1: Verify document has usable text
|
||||||
if (!hasUsableText(extraction.extractedText())) {
|
if (!hasUsableText(extraction.extractedText())) {
|
||||||
return new M3PreCheckFailed(
|
return new PreCheckFailed(
|
||||||
candidate,
|
candidate,
|
||||||
M3PreCheckFailureReason.NO_USABLE_TEXT.getDescription()
|
PreCheckFailureReason.NO_USABLE_TEXT.getDescription()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre-check 2: Verify document page count does not exceed configured limit
|
// Pre-check 2: Verify document page count does not exceed configured limit
|
||||||
if (extraction.pageCount().exceedsLimit(configuration.maxPages())) {
|
if (extraction.pageCount().exceedsLimit(configuration.maxPages())) {
|
||||||
return new M3PreCheckFailed(
|
return new PreCheckFailed(
|
||||||
candidate,
|
candidate,
|
||||||
M3PreCheckFailureReason.PAGE_LIMIT_EXCEEDED.getDescription()
|
PreCheckFailureReason.PAGE_LIMIT_EXCEEDED.getDescription()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// All pre-checks passed
|
// 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.
|
* Determines whether the extracted text contains at least one meaningful character.
|
||||||
* <p>
|
* <p>
|
||||||
* Definition of "usable text" for M3:
|
* Definition of "usable text":
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>After normalization (trimming whitespace), at least one letter or digit remains</li>
|
* <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>
|
* <li>Pure whitespace or only special characters do not qualify as usable text</li>
|
||||||
@@ -116,7 +114,7 @@ public class M3PreCheckEvaluator {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private M3PreCheckEvaluator() {
|
private PreCheckEvaluator() {
|
||||||
// Static utility class – no instances
|
// Static utility class – no instances
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,19 +12,17 @@
|
|||||||
*
|
*
|
||||||
* Current services:
|
* Current services:
|
||||||
* <ul>
|
* <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.PreCheckEvaluator} — Pre-check evaluation</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.DocumentProcessingService} — Complete document processing pipeline orchestration</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* M3 Document Processing Pipeline (M3-AP-006):
|
* Document Processing Pipeline:
|
||||||
* The {@link de.gecheckt.pdf.umbenenner.application.service.M3DocumentProcessingService} coordinates
|
* The {@link de.gecheckt.pdf.umbenenner.application.service.DocumentProcessingService} coordinates
|
||||||
* the complete M3 processing workflow:
|
* the complete processing workflow:
|
||||||
* <ol>
|
* <ol>
|
||||||
* <li>Convert technical PDF extraction results to M3 processing outcomes</li>
|
* <li>Convert technical PDF extraction results to processing outcomes</li>
|
||||||
* <li>Route successful extractions through M3 pre-check validation</li>
|
* <li>Route successful extractions through pre-check validation</li>
|
||||||
* <li>Classify extraction and pre-check failures with appropriate error types</li>
|
* <li>Classify extraction and pre-check failures with appropriate error types</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
*
|
|
||||||
* @since M3-AP-004
|
|
||||||
*/
|
*/
|
||||||
package de.gecheckt.pdf.umbenenner.application.service;
|
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.RunLockUnavailableException;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.SourceDocumentAccessException;
|
import de.gecheckt.pdf.umbenenner.application.port.out.SourceDocumentAccessException;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.SourceDocumentCandidatesPort;
|
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.BatchRunContext;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.M3DocumentProcessingOutcome;
|
import de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckFailed;
|
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailed;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckPassed;
|
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckPassed;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.M3TechnicalDocumentError;
|
import de.gecheckt.pdf.umbenenner.domain.model.TechnicalDocumentError;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionContentError;
|
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionContentError;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionResult;
|
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionResult;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess;
|
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess;
|
||||||
@@ -26,38 +26,36 @@ import org.apache.logging.log4j.Logger;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* M3 batch processing implementation of {@link RunBatchProcessingUseCase}.
|
* Batch processing implementation of {@link RunBatchProcessingUseCase}.
|
||||||
* <p>
|
* <p>
|
||||||
* Orchestrates the complete M3 batch processing workflow:
|
* Orchestrates the complete batch processing workflow:
|
||||||
* <ol>
|
* <ol>
|
||||||
* <li>Acquire exclusive run lock to prevent concurrent instances</li>
|
* <li>Acquire exclusive run lock to prevent concurrent instances</li>
|
||||||
* <li>Scan source folder for PDF candidates</li>
|
* <li>Scan source folder for PDF candidates</li>
|
||||||
* <li>For each candidate: extract text and page count, run M3 pre-checks</li>
|
* <li>For each candidate: extract text and page count, run pre-checks</li>
|
||||||
* <li>Log per-document M3 decision; end each document controlled without KI or target copy</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>
|
* <li>Release lock and return structured outcome for Bootstrap exit code mapping</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
* <p>
|
* <p>
|
||||||
* M3 processing boundary:
|
* Processing boundary:
|
||||||
* <ul>
|
* <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 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>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>
|
* <li>If the source folder itself is inaccessible, the run fails with {@link BatchRunOutcome#FAILURE}</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* <p>
|
* <p>
|
||||||
* M3 Non-Goals (not implemented):
|
* Non-Goals (not implemented):
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>No fingerprinting or SQLite persistence</li>
|
* <li>No fingerprinting or SQLite persistence</li>
|
||||||
* <li>No KI/AI integration or prompt loading</li>
|
* <li>No KI/AI integration or prompt loading</li>
|
||||||
* <li>No filename generation or target file copy</li>
|
* <li>No filename generation or target file copy</li>
|
||||||
* <li>No cross-run retry logic</li>
|
* <li>No cross-run retry logic</li>
|
||||||
* </ul>
|
* </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 StartConfiguration configuration;
|
||||||
private final RunLockPort runLockPort;
|
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
|
* @param pdfTextExtractionPort for extracting text and page count from a single PDF
|
||||||
* @throws NullPointerException if any parameter is null
|
* @throws NullPointerException if any parameter is null
|
||||||
*/
|
*/
|
||||||
public M2BatchRunProcessingUseCase(
|
public BatchRunProcessingUseCase(
|
||||||
StartConfiguration configuration,
|
StartConfiguration configuration,
|
||||||
RunLockPort runLockPort,
|
RunLockPort runLockPort,
|
||||||
SourceDocumentCandidatesPort sourceDocumentCandidatesPort,
|
SourceDocumentCandidatesPort sourceDocumentCandidatesPort,
|
||||||
@@ -116,7 +114,7 @@ public class M2BatchRunProcessingUseCase implements RunBatchProcessingUseCase {
|
|||||||
}
|
}
|
||||||
LOG.info("Found {} PDF candidate(s) in source folder.", candidates.size());
|
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) {
|
for (SourceDocumentCandidate candidate : candidates) {
|
||||||
processCandidate(candidate);
|
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>
|
* <p>
|
||||||
* M3 processing steps per document:
|
* Processing steps per document:
|
||||||
* <ol>
|
* <ol>
|
||||||
* <li>Log candidate recognition</li>
|
* <li>Log candidate recognition</li>
|
||||||
* <li>Extract text and page count from the PDF via {@link PdfTextExtractionPort}</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>Process extraction result through pre-checks via {@link DocumentProcessingService}</li>
|
||||||
* <li>Log extraction outcome and final M3 decision</li>
|
* <li>Log extraction outcome and final decision</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
* <p>
|
* <p>
|
||||||
* Per-document errors (extraction failure, technical error, pre-check failure) do not abort the overall
|
* 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.
|
* batch run. Each candidate ends controlled regardless of its outcome.
|
||||||
* <p>
|
* <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.
|
* no target file copy is initiated here, even for candidates that pass all pre-checks.
|
||||||
*
|
*
|
||||||
* @param candidate the candidate to process
|
* @param candidate the candidate to process
|
||||||
@@ -179,19 +177,19 @@ public class M2BatchRunProcessingUseCase implements RunBatchProcessingUseCase {
|
|||||||
candidate.uniqueIdentifier(), technicalError.errorMessage());
|
candidate.uniqueIdentifier(), technicalError.errorMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process through complete M3 pipeline
|
// Process through complete pipeline
|
||||||
var m3Outcome = M3DocumentProcessingService.processDocument(candidate, extractionResult, configuration);
|
var outcome = DocumentProcessingService.processDocument(candidate, extractionResult, configuration);
|
||||||
|
|
||||||
// Log M3 processing outcome
|
// Log processing outcome
|
||||||
switch (m3Outcome) {
|
switch (outcome) {
|
||||||
case M3PreCheckPassed passed ->
|
case PreCheckPassed passed ->
|
||||||
LOG.info("M3 pre-checks PASSED for '{}'. Candidate ready for further processing (M4+).",
|
LOG.info("Pre-checks PASSED for '{}'. Candidate ready for further processing.",
|
||||||
candidate.uniqueIdentifier());
|
candidate.uniqueIdentifier());
|
||||||
case M3PreCheckFailed failed ->
|
case PreCheckFailed failed ->
|
||||||
LOG.info("M3 pre-checks FAILED for '{}': {} (Deterministic content error – may retry in later run).",
|
LOG.info("Pre-checks FAILED for '{}': {} (Deterministic content error – may retry in later run).",
|
||||||
candidate.uniqueIdentifier(), failed.failureReason());
|
candidate.uniqueIdentifier(), failed.failureReason());
|
||||||
case M3TechnicalDocumentError technicalError ->
|
case TechnicalDocumentError technicalError ->
|
||||||
LOG.warn("M3 processing FAILED for '{}': {} (Technical error – may retry in later run).",
|
LOG.warn("Processing FAILED for '{}': {} (Technical error – may retry in later run).",
|
||||||
candidate.uniqueIdentifier(), technicalError.errorMessage());
|
candidate.uniqueIdentifier(), technicalError.errorMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,8 +5,8 @@
|
|||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@link de.gecheckt.pdf.umbenenner.application.usecase.NoOpRunBatchProcessingUseCase}
|
* <li>{@link de.gecheckt.pdf.umbenenner.application.usecase.NoOpRunBatchProcessingUseCase}
|
||||||
* — Minimal no-op for technical validation without start protection</li>
|
* — Minimal no-op for technical validation without start protection</li>
|
||||||
* <li>{@link de.gecheckt.pdf.umbenenner.application.usecase.M2BatchRunProcessingUseCase}
|
* <li>{@link de.gecheckt.pdf.umbenenner.application.usecase.BatchRunProcessingUseCase}
|
||||||
* — M2 production implementation with run lock and controlled batch cycle (AP-004)</li>
|
* — Production implementation with run lock and controlled batch cycle</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* <p>
|
* <p>
|
||||||
* All implementations are infrastructure-agnostic and interact only through ports.
|
* All implementations are infrastructure-agnostic and interact only through ports.
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.application.service;
|
package de.gecheckt.pdf.umbenenner.application.service;
|
||||||
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
|
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.M3DocumentProcessingOutcome;
|
import de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckFailed;
|
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailed;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckPassed;
|
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckPassed;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.M3TechnicalDocumentError;
|
import de.gecheckt.pdf.umbenenner.domain.model.TechnicalDocumentError;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionContentError;
|
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionContentError;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess;
|
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionTechnicalError;
|
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionTechnicalError;
|
||||||
@@ -23,11 +23,11 @@ import java.nio.file.Path;
|
|||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link M3DocumentProcessingService}.
|
* Tests for {@link DocumentProcessingService}.
|
||||||
* <p>
|
* <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
|
@TempDir
|
||||||
Path tempDir;
|
Path tempDir;
|
||||||
@@ -75,12 +75,12 @@ class M3DocumentProcessingServiceTest {
|
|||||||
var extraction = new PdfExtractionSuccess("This is valid PDF text", new PdfPageCount(5));
|
var extraction = new PdfExtractionSuccess("This is valid PDF text", new PdfPageCount(5));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
M3DocumentProcessingOutcome outcome = M3DocumentProcessingService.processDocument(
|
DocumentProcessingOutcome outcome = DocumentProcessingService.processDocument(
|
||||||
candidate, extraction, configuration);
|
candidate, extraction, configuration);
|
||||||
|
|
||||||
// Assert: Should produce M3PreCheckPassed
|
// Assert: Should produce PreCheckPassed
|
||||||
assertInstanceOf(M3PreCheckPassed.class, outcome);
|
assertInstanceOf(PreCheckPassed.class, outcome);
|
||||||
M3PreCheckPassed passed = (M3PreCheckPassed) outcome;
|
PreCheckPassed passed = (PreCheckPassed) outcome;
|
||||||
assertEquals(candidate, passed.candidate());
|
assertEquals(candidate, passed.candidate());
|
||||||
assertEquals(extraction, passed.extraction());
|
assertEquals(extraction, passed.extraction());
|
||||||
}
|
}
|
||||||
@@ -91,12 +91,12 @@ class M3DocumentProcessingServiceTest {
|
|||||||
var extraction = new PdfExtractionSuccess(" \n \t ", new PdfPageCount(1));
|
var extraction = new PdfExtractionSuccess(" \n \t ", new PdfPageCount(1));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
M3DocumentProcessingOutcome outcome = M3DocumentProcessingService.processDocument(
|
DocumentProcessingOutcome outcome = DocumentProcessingService.processDocument(
|
||||||
candidate, extraction, configuration);
|
candidate, extraction, configuration);
|
||||||
|
|
||||||
// Assert: Should produce M3PreCheckFailed with appropriate reason
|
// Assert: Should produce PreCheckFailed with appropriate reason
|
||||||
assertInstanceOf(M3PreCheckFailed.class, outcome);
|
assertInstanceOf(PreCheckFailed.class, outcome);
|
||||||
M3PreCheckFailed failed = (M3PreCheckFailed) outcome;
|
PreCheckFailed failed = (PreCheckFailed) outcome;
|
||||||
assertEquals(candidate, failed.candidate());
|
assertEquals(candidate, failed.candidate());
|
||||||
assertTrue(failed.failureReason().toLowerCase().contains("usable"));
|
assertTrue(failed.failureReason().toLowerCase().contains("usable"));
|
||||||
}
|
}
|
||||||
@@ -107,28 +107,28 @@ class M3DocumentProcessingServiceTest {
|
|||||||
var extraction = new PdfExtractionSuccess("Valid text content", new PdfPageCount(50));
|
var extraction = new PdfExtractionSuccess("Valid text content", new PdfPageCount(50));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
M3DocumentProcessingOutcome outcome = M3DocumentProcessingService.processDocument(
|
DocumentProcessingOutcome outcome = DocumentProcessingService.processDocument(
|
||||||
candidate, extraction, configuration);
|
candidate, extraction, configuration);
|
||||||
|
|
||||||
// Assert: Should produce M3PreCheckFailed with page limit reason
|
// Assert: Should produce PreCheckFailed with page limit reason
|
||||||
assertInstanceOf(M3PreCheckFailed.class, outcome);
|
assertInstanceOf(PreCheckFailed.class, outcome);
|
||||||
M3PreCheckFailed failed = (M3PreCheckFailed) outcome;
|
PreCheckFailed failed = (PreCheckFailed) outcome;
|
||||||
assertEquals(candidate, failed.candidate());
|
assertEquals(candidate, failed.candidate());
|
||||||
assertTrue(failed.failureReason().toLowerCase().contains("page"));
|
assertTrue(failed.failureReason().toLowerCase().contains("page"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testProcessDocument_WithContentError() {
|
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");
|
var contentError = new PdfExtractionContentError("PDF is corrupted");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
M3DocumentProcessingOutcome outcome = M3DocumentProcessingService.processDocument(
|
DocumentProcessingOutcome outcome = DocumentProcessingService.processDocument(
|
||||||
candidate, contentError, configuration);
|
candidate, contentError, configuration);
|
||||||
|
|
||||||
// Assert: Should produce M3TechnicalDocumentError
|
// Assert: Should produce TechnicalDocumentError
|
||||||
assertInstanceOf(M3TechnicalDocumentError.class, outcome);
|
assertInstanceOf(TechnicalDocumentError.class, outcome);
|
||||||
M3TechnicalDocumentError result = (M3TechnicalDocumentError) outcome;
|
TechnicalDocumentError result = (TechnicalDocumentError) outcome;
|
||||||
assertEquals(candidate, result.candidate());
|
assertEquals(candidate, result.candidate());
|
||||||
assertTrue(result.errorMessage().contains("PDF is corrupted"));
|
assertTrue(result.errorMessage().contains("PDF is corrupted"));
|
||||||
}
|
}
|
||||||
@@ -140,12 +140,12 @@ class M3DocumentProcessingServiceTest {
|
|||||||
new RuntimeException("File not found"));
|
new RuntimeException("File not found"));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
M3DocumentProcessingOutcome outcome = M3DocumentProcessingService.processDocument(
|
DocumentProcessingOutcome outcome = DocumentProcessingService.processDocument(
|
||||||
candidate, technicalError, configuration);
|
candidate, technicalError, configuration);
|
||||||
|
|
||||||
// Assert: Should produce M3TechnicalDocumentError
|
// Assert: Should produce TechnicalDocumentError
|
||||||
assertInstanceOf(M3TechnicalDocumentError.class, outcome);
|
assertInstanceOf(TechnicalDocumentError.class, outcome);
|
||||||
M3TechnicalDocumentError result = (M3TechnicalDocumentError) outcome;
|
TechnicalDocumentError result = (TechnicalDocumentError) outcome;
|
||||||
assertEquals(candidate, result.candidate());
|
assertEquals(candidate, result.candidate());
|
||||||
assertEquals("I/O error reading file", result.errorMessage());
|
assertEquals("I/O error reading file", result.errorMessage());
|
||||||
assertNotNull(result.cause());
|
assertNotNull(result.cause());
|
||||||
@@ -157,12 +157,12 @@ class M3DocumentProcessingServiceTest {
|
|||||||
var technicalError = new PdfExtractionTechnicalError("Unknown error", null);
|
var technicalError = new PdfExtractionTechnicalError("Unknown error", null);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
M3DocumentProcessingOutcome outcome = M3DocumentProcessingService.processDocument(
|
DocumentProcessingOutcome outcome = DocumentProcessingService.processDocument(
|
||||||
candidate, technicalError, configuration);
|
candidate, technicalError, configuration);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
assertInstanceOf(M3TechnicalDocumentError.class, outcome);
|
assertInstanceOf(TechnicalDocumentError.class, outcome);
|
||||||
M3TechnicalDocumentError result = (M3TechnicalDocumentError) outcome;
|
TechnicalDocumentError result = (TechnicalDocumentError) outcome;
|
||||||
assertNull(result.cause());
|
assertNull(result.cause());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,14 +173,14 @@ class M3DocumentProcessingServiceTest {
|
|||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
assertThrows(NullPointerException.class,
|
assertThrows(NullPointerException.class,
|
||||||
() -> M3DocumentProcessingService.processDocument(null, extraction, configuration));
|
() -> DocumentProcessingService.processDocument(null, extraction, configuration));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testProcessDocument_WithNullExtractionResult_ThrowsException() {
|
void testProcessDocument_WithNullExtractionResult_ThrowsException() {
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
assertThrows(NullPointerException.class,
|
assertThrows(NullPointerException.class,
|
||||||
() -> M3DocumentProcessingService.processDocument(candidate, null, configuration));
|
() -> DocumentProcessingService.processDocument(candidate, null, configuration));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -190,6 +190,6 @@ class M3DocumentProcessingServiceTest {
|
|||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
assertThrows(NullPointerException.class,
|
assertThrows(NullPointerException.class,
|
||||||
() -> M3DocumentProcessingService.processDocument(candidate, extraction, null));
|
() -> DocumentProcessingService.processDocument(candidate, extraction, null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.application.service;
|
package de.gecheckt.pdf.umbenenner.application.service;
|
||||||
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
|
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.M3DocumentProcessingOutcome;
|
import de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckFailed;
|
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailed;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckFailureReason;
|
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailureReason;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.M3PreCheckPassed;
|
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckPassed;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess;
|
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.PdfPageCount;
|
import de.gecheckt.pdf.umbenenner.domain.model.PdfPageCount;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
|
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
|
||||||
@@ -20,11 +20,11 @@ import java.nio.file.Path;
|
|||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link M3PreCheckEvaluator}.
|
* Tests for {@link PreCheckEvaluator}.
|
||||||
* <p>
|
* <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
|
@TempDir
|
||||||
Path tempDir;
|
Path tempDir;
|
||||||
@@ -35,10 +35,10 @@ class M3PreCheckEvaluatorTest {
|
|||||||
SourceDocumentCandidate candidate = buildCandidate();
|
SourceDocumentCandidate candidate = buildCandidate();
|
||||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("Some meaningful text", new PdfPageCount(5));
|
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");
|
assertTrue(result instanceof PreCheckPassed, "Should pass when text is usable and page count is valid");
|
||||||
M3PreCheckPassed passed = (M3PreCheckPassed) result;
|
PreCheckPassed passed = (PreCheckPassed) result;
|
||||||
assertSame(passed.candidate(), candidate, "Candidate should be preserved");
|
assertSame(passed.candidate(), candidate, "Candidate should be preserved");
|
||||||
assertSame(passed.extraction(), extraction, "Extraction should be preserved");
|
assertSame(passed.extraction(), extraction, "Extraction should be preserved");
|
||||||
}
|
}
|
||||||
@@ -49,11 +49,11 @@ class M3PreCheckEvaluatorTest {
|
|||||||
SourceDocumentCandidate candidate = buildCandidate();
|
SourceDocumentCandidate candidate = buildCandidate();
|
||||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("", new PdfPageCount(1));
|
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");
|
assertTrue(result instanceof PreCheckFailed, "Should fail with empty text");
|
||||||
M3PreCheckFailed failed = (M3PreCheckFailed) result;
|
PreCheckFailed failed = (PreCheckFailed) result;
|
||||||
assertEquals(M3PreCheckFailureReason.NO_USABLE_TEXT.getDescription(), failed.failureReason());
|
assertEquals(PreCheckFailureReason.NO_USABLE_TEXT.getDescription(), failed.failureReason());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -62,11 +62,11 @@ class M3PreCheckEvaluatorTest {
|
|||||||
SourceDocumentCandidate candidate = buildCandidate();
|
SourceDocumentCandidate candidate = buildCandidate();
|
||||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess(" \n\t \r\n ", new PdfPageCount(1));
|
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");
|
assertTrue(result instanceof PreCheckFailed, "Should fail with whitespace-only text");
|
||||||
M3PreCheckFailed failed = (M3PreCheckFailed) result;
|
PreCheckFailed failed = (PreCheckFailed) result;
|
||||||
assertEquals(M3PreCheckFailureReason.NO_USABLE_TEXT.getDescription(), failed.failureReason());
|
assertEquals(PreCheckFailureReason.NO_USABLE_TEXT.getDescription(), failed.failureReason());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -75,11 +75,11 @@ class M3PreCheckEvaluatorTest {
|
|||||||
SourceDocumentCandidate candidate = buildCandidate();
|
SourceDocumentCandidate candidate = buildCandidate();
|
||||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("!@#$%^&*()_+-=[]{}|;:',.<>?/", new PdfPageCount(1));
|
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");
|
assertTrue(result instanceof PreCheckFailed, "Should fail with special characters only");
|
||||||
M3PreCheckFailed failed = (M3PreCheckFailed) result;
|
PreCheckFailed failed = (PreCheckFailed) result;
|
||||||
assertEquals(M3PreCheckFailureReason.NO_USABLE_TEXT.getDescription(), failed.failureReason());
|
assertEquals(PreCheckFailureReason.NO_USABLE_TEXT.getDescription(), failed.failureReason());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -88,9 +88,9 @@ class M3PreCheckEvaluatorTest {
|
|||||||
SourceDocumentCandidate candidate = buildCandidate();
|
SourceDocumentCandidate candidate = buildCandidate();
|
||||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("a", new PdfPageCount(1));
|
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
|
@Test
|
||||||
@@ -99,9 +99,9 @@ class M3PreCheckEvaluatorTest {
|
|||||||
SourceDocumentCandidate candidate = buildCandidate();
|
SourceDocumentCandidate candidate = buildCandidate();
|
||||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("5", new PdfPageCount(1));
|
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
|
@Test
|
||||||
@@ -110,9 +110,9 @@ class M3PreCheckEvaluatorTest {
|
|||||||
SourceDocumentCandidate candidate = buildCandidate();
|
SourceDocumentCandidate candidate = buildCandidate();
|
||||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("!@#a$%^&*", new PdfPageCount(1));
|
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
|
@Test
|
||||||
@@ -121,9 +121,9 @@ class M3PreCheckEvaluatorTest {
|
|||||||
SourceDocumentCandidate candidate = buildCandidate();
|
SourceDocumentCandidate candidate = buildCandidate();
|
||||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess(" meaningful text ", new PdfPageCount(1));
|
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
|
@Test
|
||||||
@@ -132,9 +132,9 @@ class M3PreCheckEvaluatorTest {
|
|||||||
SourceDocumentCandidate candidate = buildCandidate();
|
SourceDocumentCandidate candidate = buildCandidate();
|
||||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("Valid text", new PdfPageCount(5));
|
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
|
@Test
|
||||||
@@ -143,11 +143,11 @@ class M3PreCheckEvaluatorTest {
|
|||||||
SourceDocumentCandidate candidate = buildCandidate();
|
SourceDocumentCandidate candidate = buildCandidate();
|
||||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("Valid text", new PdfPageCount(6));
|
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");
|
assertTrue(result instanceof PreCheckFailed, "Should fail when page count exceeds limit");
|
||||||
M3PreCheckFailed failed = (M3PreCheckFailed) result;
|
PreCheckFailed failed = (PreCheckFailed) result;
|
||||||
assertEquals(M3PreCheckFailureReason.PAGE_LIMIT_EXCEEDED.getDescription(), failed.failureReason());
|
assertEquals(PreCheckFailureReason.PAGE_LIMIT_EXCEEDED.getDescription(), failed.failureReason());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -156,11 +156,11 @@ class M3PreCheckEvaluatorTest {
|
|||||||
SourceDocumentCandidate candidate = buildCandidate();
|
SourceDocumentCandidate candidate = buildCandidate();
|
||||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("Excellent meaningful text with lots of content", new PdfPageCount(100));
|
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");
|
assertTrue(result instanceof PreCheckFailed, "Should fail with page limit exceeded even if text is good");
|
||||||
M3PreCheckFailed failed = (M3PreCheckFailed) result;
|
PreCheckFailed failed = (PreCheckFailed) result;
|
||||||
assertEquals(M3PreCheckFailureReason.PAGE_LIMIT_EXCEEDED.getDescription(), failed.failureReason());
|
assertEquals(PreCheckFailureReason.PAGE_LIMIT_EXCEEDED.getDescription(), failed.failureReason());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -171,11 +171,11 @@ class M3PreCheckEvaluatorTest {
|
|||||||
SourceDocumentCandidate candidate = buildCandidate();
|
SourceDocumentCandidate candidate = buildCandidate();
|
||||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("", new PdfPageCount(10));
|
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");
|
assertTrue(result instanceof PreCheckFailed, "Should fail when both checks fail");
|
||||||
// The specific order of checks doesn't matter for M3; just verify one reason is returned
|
// The specific order of checks doesn't matter; just verify one reason is returned
|
||||||
M3PreCheckFailed failed = (M3PreCheckFailed) result;
|
PreCheckFailed failed = (PreCheckFailed) result;
|
||||||
assertNotNull(failed.failureReason());
|
assertNotNull(failed.failureReason());
|
||||||
assertFalse(failed.failureReason().isEmpty());
|
assertFalse(failed.failureReason().isEmpty());
|
||||||
}
|
}
|
||||||
@@ -186,7 +186,7 @@ class M3PreCheckEvaluatorTest {
|
|||||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("Valid text", new PdfPageCount(1));
|
PdfExtractionSuccess extraction = new PdfExtractionSuccess("Valid text", new PdfPageCount(1));
|
||||||
|
|
||||||
assertThrows(NullPointerException.class, () ->
|
assertThrows(NullPointerException.class, () ->
|
||||||
M3PreCheckEvaluator.evaluate(null, extraction, config)
|
PreCheckEvaluator.evaluate(null, extraction, config)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ class M3PreCheckEvaluatorTest {
|
|||||||
SourceDocumentCandidate candidate = buildCandidate();
|
SourceDocumentCandidate candidate = buildCandidate();
|
||||||
|
|
||||||
assertThrows(NullPointerException.class, () ->
|
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));
|
PdfExtractionSuccess extraction = new PdfExtractionSuccess("Valid text", new PdfPageCount(1));
|
||||||
|
|
||||||
assertThrows(NullPointerException.class, () ->
|
assertThrows(NullPointerException.class, () ->
|
||||||
M3PreCheckEvaluator.evaluate(candidate, extraction, null)
|
PreCheckEvaluator.evaluate(candidate, extraction, null)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,9 +216,9 @@ class M3PreCheckEvaluatorTest {
|
|||||||
SourceDocumentCandidate candidate = buildCandidate();
|
SourceDocumentCandidate candidate = buildCandidate();
|
||||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("Äußerst äöüß Großes", new PdfPageCount(1));
|
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
|
@Test
|
||||||
@@ -227,9 +227,9 @@ class M3PreCheckEvaluatorTest {
|
|||||||
SourceDocumentCandidate candidate = buildCandidate();
|
SourceDocumentCandidate candidate = buildCandidate();
|
||||||
PdfExtractionSuccess extraction = new PdfExtractionSuccess("Αβγδ 中文 καλημέρα", new PdfPageCount(1));
|
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -31,25 +31,25 @@ import java.util.Map;
|
|||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link M2BatchRunProcessingUseCase}.
|
* Tests for {@link BatchRunProcessingUseCase}.
|
||||||
* <p>
|
* <p>
|
||||||
* Covers:
|
* Covers:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Lock acquisition and release lifecycle (M2)</li>
|
* <li>Lock acquisition and release lifecycle</li>
|
||||||
* <li>M3 source folder scanning and per-document processing loop</li>
|
* <li>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>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>Deterministic content errors: no usable text, page limit exceeded</li>
|
||||||
* <li>M3 technical extraction errors: controlled per-document end, batch continues</li>
|
* <li>Technical extraction errors: controlled per-document end, batch continues</li>
|
||||||
* <li>Source folder access failure: batch fails with FAILURE outcome</li>
|
* <li>Source folder access failure: batch fails with FAILURE outcome</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
class M2BatchRunProcessingUseCaseTest {
|
class BatchRunProcessingUseCaseTest {
|
||||||
|
|
||||||
@TempDir
|
@TempDir
|
||||||
Path tempDir;
|
Path tempDir;
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// M2: Lock lifecycle tests (preserved, updated constructor)
|
// Lock lifecycle tests
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -57,7 +57,7 @@ class M2BatchRunProcessingUseCaseTest {
|
|||||||
MockRunLockPort lockPort = new MockRunLockPort();
|
MockRunLockPort lockPort = new MockRunLockPort();
|
||||||
StartConfiguration config = buildConfig(tempDir);
|
StartConfiguration config = buildConfig(tempDir);
|
||||||
|
|
||||||
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
BatchRunProcessingUseCase useCase = new BatchRunProcessingUseCase(
|
||||||
config, lockPort, new EmptyCandidatesPort(), new NoOpExtractionPort());
|
config, lockPort, new EmptyCandidatesPort(), new NoOpExtractionPort());
|
||||||
BatchRunContext context = new BatchRunContext(new RunId("test-run-1"), Instant.now());
|
BatchRunContext context = new BatchRunContext(new RunId("test-run-1"), Instant.now());
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ class M2BatchRunProcessingUseCaseTest {
|
|||||||
CountingRunLockPort lockPort = new CountingRunLockPort(true);
|
CountingRunLockPort lockPort = new CountingRunLockPort(true);
|
||||||
StartConfiguration config = buildConfig(tempDir);
|
StartConfiguration config = buildConfig(tempDir);
|
||||||
|
|
||||||
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
BatchRunProcessingUseCase useCase = new BatchRunProcessingUseCase(
|
||||||
config, lockPort, new EmptyCandidatesPort(), new NoOpExtractionPort());
|
config, lockPort, new EmptyCandidatesPort(), new NoOpExtractionPort());
|
||||||
BatchRunContext context = new BatchRunContext(new RunId("test-run-2"), Instant.now());
|
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
|
@Test
|
||||||
void execute_doesNotReleaseLockWhenAcquireFails() throws Exception {
|
void execute_doesNotReleaseLockWhenAcquireFails() throws Exception {
|
||||||
CountingRunLockPort lockPort = new CountingRunLockPort(true);
|
CountingRunLockPort lockPort = new CountingRunLockPort(true);
|
||||||
StartConfiguration config = buildConfig(tempDir);
|
StartConfiguration config = buildConfig(tempDir);
|
||||||
|
|
||||||
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
BatchRunProcessingUseCase useCase = new BatchRunProcessingUseCase(
|
||||||
config, lockPort, new EmptyCandidatesPort(), new NoOpExtractionPort());
|
config, lockPort, new EmptyCandidatesPort(), new NoOpExtractionPort());
|
||||||
BatchRunContext context = new BatchRunContext(new RunId("test-run-f1"), Instant.now());
|
BatchRunContext context = new BatchRunContext(new RunId("test-run-f1"), Instant.now());
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ class M2BatchRunProcessingUseCaseTest {
|
|||||||
ErrorAfterAcquireLockPort lockPort = new ErrorAfterAcquireLockPort();
|
ErrorAfterAcquireLockPort lockPort = new ErrorAfterAcquireLockPort();
|
||||||
StartConfiguration config = buildConfig(tempDir);
|
StartConfiguration config = buildConfig(tempDir);
|
||||||
|
|
||||||
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
BatchRunProcessingUseCase useCase = new BatchRunProcessingUseCase(
|
||||||
config, lockPort, new EmptyCandidatesPort(), new NoOpExtractionPort());
|
config, lockPort, new EmptyCandidatesPort(), new NoOpExtractionPort());
|
||||||
BatchRunContext context = new BatchRunContext(new RunId("test-run-3"), Instant.now());
|
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
|
@Test
|
||||||
@@ -128,9 +128,9 @@ class M2BatchRunProcessingUseCaseTest {
|
|||||||
MockRunLockPort lockPort = new MockRunLockPort();
|
MockRunLockPort lockPort = new MockRunLockPort();
|
||||||
StartConfiguration config = buildConfig(tempDir);
|
StartConfiguration config = buildConfig(tempDir);
|
||||||
|
|
||||||
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
BatchRunProcessingUseCase useCase = new BatchRunProcessingUseCase(
|
||||||
config, lockPort, new EmptyCandidatesPort(), new NoOpExtractionPort());
|
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);
|
BatchRunOutcome outcome = useCase.execute(context);
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ class M2BatchRunProcessingUseCaseTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void execute_m3HappyPath_candidatePassesPreChecks_endsControlledWithoutKiOrCopy() throws Exception {
|
void execute_happyPath_candidatePassesPreChecks_endsControlledWithoutKiOrCopy() throws Exception {
|
||||||
MockRunLockPort lockPort = new MockRunLockPort();
|
MockRunLockPort lockPort = new MockRunLockPort();
|
||||||
StartConfiguration config = buildConfig(tempDir);
|
StartConfiguration config = buildConfig(tempDir);
|
||||||
|
|
||||||
@@ -148,19 +148,19 @@ class M2BatchRunProcessingUseCaseTest {
|
|||||||
FixedCandidatesPort candidatesPort = new FixedCandidatesPort(List.of(candidate));
|
FixedCandidatesPort candidatesPort = new FixedCandidatesPort(List.of(candidate));
|
||||||
FixedExtractionPort extractionPort = new FixedExtractionPort(success);
|
FixedExtractionPort extractionPort = new FixedExtractionPort(success);
|
||||||
|
|
||||||
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
BatchRunProcessingUseCase useCase = new BatchRunProcessingUseCase(
|
||||||
config, lockPort, candidatesPort, extractionPort);
|
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);
|
BatchRunOutcome outcome = useCase.execute(context);
|
||||||
|
|
||||||
// Batch run succeeds; document ended controlled at M3 boundary (no KI, no copy)
|
// Batch run succeeds; document ended controlled at boundary (no KI, no copy)
|
||||||
assertTrue(outcome.isSuccess(), "M3 happy path should yield SUCCESS");
|
assertTrue(outcome.isSuccess(), "Happy path should yield SUCCESS");
|
||||||
assertEquals(1, extractionPort.callCount(), "Extraction should be called exactly once");
|
assertEquals(1, extractionPort.callCount(), "Extraction should be called exactly once");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void execute_m3NoUsableText_candidateEndsControlled_batchContinues() throws Exception {
|
void execute_noUsableText_candidateEndsControlled_batchContinues() throws Exception {
|
||||||
MockRunLockPort lockPort = new MockRunLockPort();
|
MockRunLockPort lockPort = new MockRunLockPort();
|
||||||
StartConfiguration config = buildConfig(tempDir);
|
StartConfiguration config = buildConfig(tempDir);
|
||||||
|
|
||||||
@@ -170,9 +170,9 @@ class M2BatchRunProcessingUseCaseTest {
|
|||||||
FixedCandidatesPort candidatesPort = new FixedCandidatesPort(List.of(candidate));
|
FixedCandidatesPort candidatesPort = new FixedCandidatesPort(List.of(candidate));
|
||||||
FixedExtractionPort extractionPort = new FixedExtractionPort(emptySuccess);
|
FixedExtractionPort extractionPort = new FixedExtractionPort(emptySuccess);
|
||||||
|
|
||||||
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
BatchRunProcessingUseCase useCase = new BatchRunProcessingUseCase(
|
||||||
config, lockPort, candidatesPort, extractionPort);
|
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);
|
BatchRunOutcome outcome = useCase.execute(context);
|
||||||
|
|
||||||
@@ -181,7 +181,7 @@ class M2BatchRunProcessingUseCaseTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void execute_m3PageLimitExceeded_candidateEndsControlled_batchContinues() throws Exception {
|
void execute_pageLimitExceeded_candidateEndsControlled_batchContinues() throws Exception {
|
||||||
MockRunLockPort lockPort = new MockRunLockPort();
|
MockRunLockPort lockPort = new MockRunLockPort();
|
||||||
// Config has maxPages=3; document has 10 pages
|
// Config has maxPages=3; document has 10 pages
|
||||||
StartConfiguration config = buildConfig(tempDir);
|
StartConfiguration config = buildConfig(tempDir);
|
||||||
@@ -191,9 +191,9 @@ class M2BatchRunProcessingUseCaseTest {
|
|||||||
FixedCandidatesPort candidatesPort = new FixedCandidatesPort(List.of(candidate));
|
FixedCandidatesPort candidatesPort = new FixedCandidatesPort(List.of(candidate));
|
||||||
FixedExtractionPort extractionPort = new FixedExtractionPort(manyPages);
|
FixedExtractionPort extractionPort = new FixedExtractionPort(manyPages);
|
||||||
|
|
||||||
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
BatchRunProcessingUseCase useCase = new BatchRunProcessingUseCase(
|
||||||
config, lockPort, candidatesPort, extractionPort);
|
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);
|
BatchRunOutcome outcome = useCase.execute(context);
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ class M2BatchRunProcessingUseCaseTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void execute_m3ExtractionContentError_candidateEndsControlled_batchContinues() throws Exception {
|
void execute_extractionContentError_candidateEndsControlled_batchContinues() throws Exception {
|
||||||
MockRunLockPort lockPort = new MockRunLockPort();
|
MockRunLockPort lockPort = new MockRunLockPort();
|
||||||
StartConfiguration config = buildConfig(tempDir);
|
StartConfiguration config = buildConfig(tempDir);
|
||||||
|
|
||||||
@@ -211,9 +211,9 @@ class M2BatchRunProcessingUseCaseTest {
|
|||||||
FixedCandidatesPort candidatesPort = new FixedCandidatesPort(List.of(candidate));
|
FixedCandidatesPort candidatesPort = new FixedCandidatesPort(List.of(candidate));
|
||||||
FixedExtractionPort extractionPort = new FixedExtractionPort(contentError);
|
FixedExtractionPort extractionPort = new FixedExtractionPort(contentError);
|
||||||
|
|
||||||
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
BatchRunProcessingUseCase useCase = new BatchRunProcessingUseCase(
|
||||||
config, lockPort, candidatesPort, extractionPort);
|
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);
|
BatchRunOutcome outcome = useCase.execute(context);
|
||||||
|
|
||||||
@@ -221,7 +221,7 @@ class M2BatchRunProcessingUseCaseTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void execute_m3ExtractionTechnicalError_candidateEndsControlled_batchContinues() throws Exception {
|
void execute_extractionTechnicalError_candidateEndsControlled_batchContinues() throws Exception {
|
||||||
MockRunLockPort lockPort = new MockRunLockPort();
|
MockRunLockPort lockPort = new MockRunLockPort();
|
||||||
StartConfiguration config = buildConfig(tempDir);
|
StartConfiguration config = buildConfig(tempDir);
|
||||||
|
|
||||||
@@ -230,9 +230,9 @@ class M2BatchRunProcessingUseCaseTest {
|
|||||||
FixedCandidatesPort candidatesPort = new FixedCandidatesPort(List.of(candidate));
|
FixedCandidatesPort candidatesPort = new FixedCandidatesPort(List.of(candidate));
|
||||||
FixedExtractionPort extractionPort = new FixedExtractionPort(technicalError);
|
FixedExtractionPort extractionPort = new FixedExtractionPort(technicalError);
|
||||||
|
|
||||||
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
BatchRunProcessingUseCase useCase = new BatchRunProcessingUseCase(
|
||||||
config, lockPort, candidatesPort, extractionPort);
|
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);
|
BatchRunOutcome outcome = useCase.execute(context);
|
||||||
|
|
||||||
@@ -240,7 +240,7 @@ class M2BatchRunProcessingUseCaseTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void execute_m3SourceAccessException_returnsFailure() throws Exception {
|
void execute_sourceAccessException_returnsFailure() throws Exception {
|
||||||
MockRunLockPort lockPort = new MockRunLockPort();
|
MockRunLockPort lockPort = new MockRunLockPort();
|
||||||
StartConfiguration config = buildConfig(tempDir);
|
StartConfiguration config = buildConfig(tempDir);
|
||||||
|
|
||||||
@@ -248,9 +248,9 @@ class M2BatchRunProcessingUseCaseTest {
|
|||||||
throw new SourceDocumentAccessException("Source folder not readable");
|
throw new SourceDocumentAccessException("Source folder not readable");
|
||||||
};
|
};
|
||||||
|
|
||||||
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
BatchRunProcessingUseCase useCase = new BatchRunProcessingUseCase(
|
||||||
config, lockPort, failingPort, new NoOpExtractionPort());
|
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);
|
BatchRunOutcome outcome = useCase.execute(context);
|
||||||
|
|
||||||
@@ -261,11 +261,11 @@ class M2BatchRunProcessingUseCaseTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mixed-batch test: one document per M3 outcome type in a single run.
|
* Mixed-batch test: one document per outcome type in a single run.
|
||||||
* Proves that no individual outcome aborts the overall batch (AP-008 explicit contract).
|
* Proves that no individual outcome aborts the overall batch.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void execute_m3MixedBatch_allOutcomeTypes_batchOverallSucceeds() throws Exception {
|
void execute_mixedBatch_allOutcomeTypes_batchOverallSucceeds() throws Exception {
|
||||||
MockRunLockPort lockPort = new MockRunLockPort();
|
MockRunLockPort lockPort = new MockRunLockPort();
|
||||||
// maxPages=3 in buildConfig; pageLimitCandidate has 10 pages → exceeds limit
|
// maxPages=3 in buildConfig; pageLimitCandidate has 10 pages → exceeds limit
|
||||||
StartConfiguration config = buildConfig(tempDir);
|
StartConfiguration config = buildConfig(tempDir);
|
||||||
@@ -287,20 +287,20 @@ class M2BatchRunProcessingUseCaseTest {
|
|||||||
.with(technicalErrorCandidate, new PdfExtractionTechnicalError("I/O error", null))
|
.with(technicalErrorCandidate, new PdfExtractionTechnicalError("I/O error", null))
|
||||||
.with(contentErrorCandidate, new PdfExtractionContentError("PDF is encrypted"));
|
.with(contentErrorCandidate, new PdfExtractionContentError("PDF is encrypted"));
|
||||||
|
|
||||||
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
BatchRunProcessingUseCase useCase = new BatchRunProcessingUseCase(
|
||||||
config, lockPort, candidatesPort, extractionPort);
|
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);
|
BatchRunOutcome outcome = useCase.execute(context);
|
||||||
|
|
||||||
assertTrue(outcome.isSuccess(),
|
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(),
|
assertEquals(5, extractionPort.callCount(),
|
||||||
"Extraction must be attempted for each of the 5 candidates");
|
"Extraction must be attempted for each of the 5 candidates");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void execute_m3MultipleCandidates_allProcessed_batchSucceeds() throws Exception {
|
void execute_multipleCandidates_allProcessed_batchSucceeds() throws Exception {
|
||||||
MockRunLockPort lockPort = new MockRunLockPort();
|
MockRunLockPort lockPort = new MockRunLockPort();
|
||||||
StartConfiguration config = buildConfig(tempDir);
|
StartConfiguration config = buildConfig(tempDir);
|
||||||
|
|
||||||
@@ -313,9 +313,9 @@ class M2BatchRunProcessingUseCaseTest {
|
|||||||
FixedCandidatesPort candidatesPort = new FixedCandidatesPort(candidates);
|
FixedCandidatesPort candidatesPort = new FixedCandidatesPort(candidates);
|
||||||
FixedExtractionPort extractionPort = new FixedExtractionPort(success);
|
FixedExtractionPort extractionPort = new FixedExtractionPort(success);
|
||||||
|
|
||||||
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(
|
BatchRunProcessingUseCase useCase = new BatchRunProcessingUseCase(
|
||||||
config, lockPort, candidatesPort, extractionPort);
|
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);
|
BatchRunOutcome outcome = useCase.execute(context);
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ import de.gecheckt.pdf.umbenenner.application.port.in.BatchRunOutcome;
|
|||||||
import de.gecheckt.pdf.umbenenner.application.port.in.RunBatchProcessingUseCase;
|
import de.gecheckt.pdf.umbenenner.application.port.in.RunBatchProcessingUseCase;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.ConfigurationPort;
|
import de.gecheckt.pdf.umbenenner.application.port.out.ConfigurationPort;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.RunLockPort;
|
import de.gecheckt.pdf.umbenenner.application.port.out.RunLockPort;
|
||||||
import de.gecheckt.pdf.umbenenner.application.usecase.M2BatchRunProcessingUseCase;
|
import de.gecheckt.pdf.umbenenner.application.usecase.BatchRunProcessingUseCase;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.BatchRunContext;
|
import de.gecheckt.pdf.umbenenner.domain.model.BatchRunContext;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.RunId;
|
import de.gecheckt.pdf.umbenenner.domain.model.RunId;
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ import java.util.UUID;
|
|||||||
* <li>Map the batch outcome to a process exit code</li>
|
* <li>Map the batch outcome to a process exit code</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
* <p>
|
* <p>
|
||||||
* Exit code semantics (M3):
|
* Exit code semantics:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@code 0}: Batch run executed successfully; individual document failures do not
|
* <li>{@code 0}: Batch run executed successfully; individual document failures do not
|
||||||
* change the exit code as long as the run itself completed without a hard infrastructure error.</li>
|
* change the exit code as long as the run itself completed without a hard infrastructure error.</li>
|
||||||
@@ -101,7 +101,7 @@ public class BootstrapRunner {
|
|||||||
/**
|
/**
|
||||||
* Creates the BootstrapRunner with default factories for production use.
|
* Creates the BootstrapRunner with default factories for production use.
|
||||||
* <p>
|
* <p>
|
||||||
* Wires the full M3 processing pipeline:
|
* Wires the full processing pipeline:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@link PropertiesConfigurationPortAdapter} for configuration loading</li>
|
* <li>{@link PropertiesConfigurationPortAdapter} for configuration loading</li>
|
||||||
* <li>{@link FilesystemRunLockPortAdapter} for exclusive run locking</li>
|
* <li>{@link FilesystemRunLockPortAdapter} for exclusive run locking</li>
|
||||||
@@ -113,7 +113,7 @@ public class BootstrapRunner {
|
|||||||
this.configPortFactory = PropertiesConfigurationPortAdapter::new;
|
this.configPortFactory = PropertiesConfigurationPortAdapter::new;
|
||||||
this.runLockPortFactory = FilesystemRunLockPortAdapter::new;
|
this.runLockPortFactory = FilesystemRunLockPortAdapter::new;
|
||||||
this.validatorFactory = StartConfigurationValidator::new;
|
this.validatorFactory = StartConfigurationValidator::new;
|
||||||
this.useCaseFactory = (config, lock) -> new M2BatchRunProcessingUseCase(
|
this.useCaseFactory = (config, lock) -> new BatchRunProcessingUseCase(
|
||||||
config,
|
config,
|
||||||
lock,
|
lock,
|
||||||
new SourceDocumentCandidatesPortAdapter(config.sourceFolder()),
|
new SourceDocumentCandidatesPortAdapter(config.sourceFolder()),
|
||||||
@@ -172,7 +172,7 @@ public class BootstrapRunner {
|
|||||||
}
|
}
|
||||||
RunLockPort runLockPort = runLockPortFactory.create(lockFilePath);
|
RunLockPort runLockPort = runLockPortFactory.create(lockFilePath);
|
||||||
|
|
||||||
// Step 5: Create the batch run context (M2-AP-003)
|
// Step 5: Create the batch run context
|
||||||
// Generate a unique run ID and initialize the run context
|
// Generate a unique run ID and initialize the run context
|
||||||
RunId runId = new RunId(UUID.randomUUID().toString());
|
RunId runId = new RunId(UUID.randomUUID().toString());
|
||||||
BatchRunContext runContext = new BatchRunContext(runId, Instant.now());
|
BatchRunContext runContext = new BatchRunContext(runId, Instant.now());
|
||||||
@@ -180,7 +180,7 @@ public class BootstrapRunner {
|
|||||||
|
|
||||||
// Step 6: Create the use case with the validated config and run lock (application layer).
|
// Step 6: Create the use case with the validated config and run lock (application layer).
|
||||||
// Config is passed directly; the use case does not re-read the properties file.
|
// Config is passed directly; the use case does not re-read the properties file.
|
||||||
// M3 adapters (source document port, PDF extraction port) are wired by the factory.
|
// Adapters (source document port, PDF extraction port) are wired by the factory.
|
||||||
RunBatchProcessingUseCase useCase = useCaseFactory.create(config, runLockPort);
|
RunBatchProcessingUseCase useCase = useCaseFactory.create(config, runLockPort);
|
||||||
|
|
||||||
// Step 7: Create the CLI command adapter with the use case
|
// Step 7: Create the CLI command adapter with the use case
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package de.gecheckt.pdf.umbenenner.domain.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sealed interface representing the complete outcome of document processing.
|
||||||
|
* <p>
|
||||||
|
* This interface models all possible document outcomes:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link PreCheckPassed}: Document passed all pre-checks</li>
|
||||||
|
* <li>{@link PreCheckFailed}: Document failed a pre-check (deterministic content error: no usable text or page limit exceeded)</li>
|
||||||
|
* <li>{@link TechnicalDocumentError}: Technical failure during candidate access or PDF extraction (I/O, access, parsing, etc.)</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* Design principles:
|
||||||
|
* <ul>
|
||||||
|
* <li>Exhaustive: All document processing outcomes are covered</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 (PreCheckFailed) vs. technical failures (TechnicalDocumentError)</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* Error classification:
|
||||||
|
* <ul>
|
||||||
|
* <li>PreCheckPassed: Extraction succeeded and all pre-checks passed (ready for further processing)</li>
|
||||||
|
* <li>PreCheckFailed: Extraction succeeded but deterministic content check failed (no usable text, page limit exceeded)</li>
|
||||||
|
* <li>TechnicalDocumentError: Extraction failed due to technical issue (I/O, file access, PDF parsing, etc.)</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public sealed interface DocumentProcessingOutcome
|
||||||
|
permits PreCheckPassed, PreCheckFailed, TechnicalDocumentError {
|
||||||
|
// Marker interface; concrete implementations define structure
|
||||||
|
}
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.domain.model;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sealed interface representing the outcome of M3 document pre-checks.
|
|
||||||
* <p>
|
|
||||||
* This interface introduced in AP-001 establishes the architectural
|
|
||||||
* pattern for M3 pre-check results. The actual pre-check logic (fachlich validation
|
|
||||||
* such as "brauchbarer Text" and "Seitenlimit") is implemented in AP-004 via
|
|
||||||
* {@link de.gecheckt.pdf.umbenenner.application.service.M3PreCheckEvaluator}.
|
|
||||||
* <p>
|
|
||||||
* There are two allowed implementations:
|
|
||||||
* <ul>
|
|
||||||
* <li>{@link M3PreCheckPassed}: Document passed all M3 pre-checks and is ready for KI integration</li>
|
|
||||||
* <li>{@link M3PreCheckFailed}: Document failed an M3 pre-check and will not proceed further in this run</li>
|
|
||||||
* </ul>
|
|
||||||
* <p>
|
|
||||||
* Design principles:
|
|
||||||
* <ul>
|
|
||||||
* <li>Sealed: enforces exhaustive handling of all cases</li>
|
|
||||||
* <li>Carries both success path ({@link M3PreCheckPassed}) and failure reason ({@link M3PreCheckFailed})</li>
|
|
||||||
* <li>Defined early (AP-001) to ensure architecture is established before logic arrives</li>
|
|
||||||
* <li>Future-extensible for additional pre-check variants in later milestones</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* @since M3-AP-001
|
|
||||||
*/
|
|
||||||
public sealed interface M3ProcessingDecision
|
|
||||||
permits M3PreCheckPassed, M3PreCheckFailed {
|
|
||||||
// Marker interface; concrete implementations define structure
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@ package de.gecheckt.pdf.umbenenner.domain.model;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a document that failed an M3 pre-check.
|
* Represents a document that failed a pre-check.
|
||||||
* <p>
|
* <p>
|
||||||
* This result encapsulates:
|
* This result encapsulates:
|
||||||
* <ul>
|
* <ul>
|
||||||
@@ -22,12 +22,11 @@ import java.util.Objects;
|
|||||||
*
|
*
|
||||||
* @param candidate the source document metadata
|
* @param candidate the source document metadata
|
||||||
* @param failureReason a human-readable explanation of the pre-check failure
|
* @param failureReason a human-readable explanation of the pre-check failure
|
||||||
* @since M3-AP-001
|
|
||||||
*/
|
*/
|
||||||
public record M3PreCheckFailed(
|
public record PreCheckFailed(
|
||||||
SourceDocumentCandidate candidate,
|
SourceDocumentCandidate candidate,
|
||||||
String failureReason
|
String failureReason
|
||||||
) implements M3ProcessingDecision, M3DocumentProcessingOutcome {
|
) implements ProcessingDecision, DocumentProcessingOutcome {
|
||||||
/**
|
/**
|
||||||
* Constructor with validation.
|
* Constructor with validation.
|
||||||
*
|
*
|
||||||
@@ -36,7 +35,7 @@ public record M3PreCheckFailed(
|
|||||||
* @throws NullPointerException if either parameter is null
|
* @throws NullPointerException if either parameter is null
|
||||||
* @throws IllegalArgumentException if failureReason is empty
|
* @throws IllegalArgumentException if failureReason is empty
|
||||||
*/
|
*/
|
||||||
public M3PreCheckFailed {
|
public PreCheckFailed {
|
||||||
Objects.requireNonNull(candidate, "candidate must not be null");
|
Objects.requireNonNull(candidate, "candidate must not be null");
|
||||||
Objects.requireNonNull(failureReason, "failureReason must not be null");
|
Objects.requireNonNull(failureReason, "failureReason must not be null");
|
||||||
if (failureReason.isEmpty()) {
|
if (failureReason.isEmpty()) {
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.domain.model;
|
package de.gecheckt.pdf.umbenenner.domain.model;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enumeration of M3 pre-check failure reasons.
|
* Enumeration of pre-check failure reasons.
|
||||||
* <p>
|
* <p>
|
||||||
* These are the deterministic content errors that can occur during M3 pre-check evaluation.
|
* These are the deterministic content errors that can occur during pre-check evaluation.
|
||||||
* They distinguish between failures in the document content versus technical extraction failures.
|
* They distinguish between failures in the document content versus technical extraction failures.
|
||||||
* <p>
|
* <p>
|
||||||
* Deterministic content errors:
|
* Deterministic content errors:
|
||||||
@@ -12,19 +12,17 @@ package de.gecheckt.pdf.umbenenner.domain.model;
|
|||||||
* <li>{@link #PAGE_LIMIT_EXCEEDED}: The document exceeds the configured page limit.</li>
|
* <li>{@link #PAGE_LIMIT_EXCEEDED}: The document exceeds the configured page limit.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* <p>
|
* <p>
|
||||||
* Note: Technical extraction failures (I/O errors, PDFBox failures) are not M3 pre-check reasons;
|
* Note: Technical extraction failures (I/O errors, PDFBox failures) are not pre-check reasons;
|
||||||
* they are represented as {@link PdfExtractionTechnicalError} in the extraction result.
|
* they are represented as {@link PdfExtractionTechnicalError} in the extraction result.
|
||||||
*
|
|
||||||
* @since M3-AP-004
|
|
||||||
*/
|
*/
|
||||||
public enum M3PreCheckFailureReason {
|
public enum PreCheckFailureReason {
|
||||||
/**
|
/**
|
||||||
* The extracted PDF text, after normalization, contains no letters or digits.
|
* The extracted PDF text, after normalization, contains no letters or digits.
|
||||||
* <p>
|
* <p>
|
||||||
* This is a deterministic content error: reprocessing the same file in a later run
|
* This is a deterministic content error: reprocessing the same file in a later run
|
||||||
* will have the same outcome unless the source file is changed.
|
* will have the same outcome unless the source file is changed.
|
||||||
* <p>
|
* <p>
|
||||||
* In M3, retry logic: exactly 1 retry in a later batch run.
|
* Retry logic: exactly 1 retry in a later batch run.
|
||||||
*/
|
*/
|
||||||
NO_USABLE_TEXT("No usable text in extracted PDF content"),
|
NO_USABLE_TEXT("No usable text in extracted PDF content"),
|
||||||
|
|
||||||
@@ -33,13 +31,13 @@ public enum M3PreCheckFailureReason {
|
|||||||
* <p>
|
* <p>
|
||||||
* This is a deterministic content error: the page count will not change unless the source file is modified.
|
* This is a deterministic content error: the page count will not change unless the source file is modified.
|
||||||
* <p>
|
* <p>
|
||||||
* In M3, retry logic: exactly 1 retry in a later batch run.
|
* Retry logic: exactly 1 retry in a later batch run.
|
||||||
*/
|
*/
|
||||||
PAGE_LIMIT_EXCEEDED("Document page count exceeds configured limit");
|
PAGE_LIMIT_EXCEEDED("Document page count exceeds configured limit");
|
||||||
|
|
||||||
private final String description;
|
private final String description;
|
||||||
|
|
||||||
M3PreCheckFailureReason(String description) {
|
PreCheckFailureReason(String description) {
|
||||||
this.description = description;
|
this.description = description;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3,7 +3,7 @@ package de.gecheckt.pdf.umbenenner.domain.model;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a document that passed all M3 pre-checks.
|
* Represents a document that passed all pre-checks.
|
||||||
* <p>
|
* <p>
|
||||||
* This result encapsulates:
|
* This result encapsulates:
|
||||||
* <ul>
|
* <ul>
|
||||||
@@ -11,17 +11,16 @@ import java.util.Objects;
|
|||||||
* <li>The successful PDF text extraction result</li>
|
* <li>The successful PDF text extraction result</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* <p>
|
* <p>
|
||||||
* A document with this decision is ready to proceed to M4 and later milestones
|
* A document with this decision is ready to proceed to further processing steps
|
||||||
* (fingerprinting, persistence, KI integration, filename generation, target copy).
|
* (fingerprinting, persistence, KI integration, filename generation, target copy).
|
||||||
*
|
*
|
||||||
* @param candidate the source document metadata
|
* @param candidate the source document metadata
|
||||||
* @param extraction the successful text extraction result
|
* @param extraction the successful text extraction result
|
||||||
* @since M3-AP-001
|
|
||||||
*/
|
*/
|
||||||
public record M3PreCheckPassed(
|
public record PreCheckPassed(
|
||||||
SourceDocumentCandidate candidate,
|
SourceDocumentCandidate candidate,
|
||||||
PdfExtractionSuccess extraction
|
PdfExtractionSuccess extraction
|
||||||
) implements M3ProcessingDecision, M3DocumentProcessingOutcome {
|
) implements ProcessingDecision, DocumentProcessingOutcome {
|
||||||
/**
|
/**
|
||||||
* Constructor with validation.
|
* Constructor with validation.
|
||||||
*
|
*
|
||||||
@@ -29,7 +28,7 @@ public record M3PreCheckPassed(
|
|||||||
* @param extraction must be non-null
|
* @param extraction must be non-null
|
||||||
* @throws NullPointerException if either parameter is null
|
* @throws NullPointerException if either parameter is null
|
||||||
*/
|
*/
|
||||||
public M3PreCheckPassed {
|
public PreCheckPassed {
|
||||||
Objects.requireNonNull(candidate, "candidate must not be null");
|
Objects.requireNonNull(candidate, "candidate must not be null");
|
||||||
Objects.requireNonNull(extraction, "extraction must not be null");
|
Objects.requireNonNull(extraction, "extraction must not be null");
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package de.gecheckt.pdf.umbenenner.domain.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sealed interface representing the outcome of document pre-checks.
|
||||||
|
* <p>
|
||||||
|
* This interface establishes the architectural pattern for pre-check results.
|
||||||
|
* The actual pre-check logic (fachlich validation such as "brauchbarer Text" and "Seitenlimit")
|
||||||
|
* is implemented via {@link de.gecheckt.pdf.umbenenner.application.service.PreCheckEvaluator}.
|
||||||
|
* <p>
|
||||||
|
* There are two allowed implementations:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link PreCheckPassed}: Document passed all pre-checks and is ready for further processing</li>
|
||||||
|
* <li>{@link PreCheckFailed}: Document failed a pre-check and will not proceed further in this run</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* Design principles:
|
||||||
|
* <ul>
|
||||||
|
* <li>Sealed: enforces exhaustive handling of all cases</li>
|
||||||
|
* <li>Carries both success path ({@link PreCheckPassed}) and failure reason ({@link PreCheckFailed})</li>
|
||||||
|
* <li>Defined early to ensure architecture is established before logic arrives</li>
|
||||||
|
* <li>Future-extensible for additional pre-check variants in later milestones</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public sealed interface ProcessingDecision
|
||||||
|
permits PreCheckPassed, PreCheckFailed {
|
||||||
|
// Marker interface; concrete implementations define structure
|
||||||
|
}
|
||||||
@@ -20,19 +20,18 @@ import java.util.Objects;
|
|||||||
* <li>Out of memory during extraction</li>
|
* <li>Out of memory during extraction</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* <p>
|
* <p>
|
||||||
* This is distinct from {@link M3ExtractedContentError}, which represents problems with the document
|
* This is distinct from content errors, which represent problems with the document
|
||||||
* content itself rather than infrastructure failures.
|
* content itself rather than infrastructure failures.
|
||||||
*
|
*
|
||||||
* @param candidate the source document metadata
|
* @param candidate the source document metadata
|
||||||
* @param errorMessage a description of the technical failure
|
* @param errorMessage a description of the technical failure
|
||||||
* @param cause the underlying exception, if any (may be null)
|
* @param cause the underlying exception, if any (may be null)
|
||||||
* @since M3-AP-006
|
|
||||||
*/
|
*/
|
||||||
public record M3TechnicalDocumentError(
|
public record TechnicalDocumentError(
|
||||||
SourceDocumentCandidate candidate,
|
SourceDocumentCandidate candidate,
|
||||||
String errorMessage,
|
String errorMessage,
|
||||||
Throwable cause
|
Throwable cause
|
||||||
) implements M3DocumentProcessingOutcome {
|
) implements DocumentProcessingOutcome {
|
||||||
/**
|
/**
|
||||||
* Constructor with validation.
|
* Constructor with validation.
|
||||||
*
|
*
|
||||||
@@ -42,7 +41,7 @@ public record M3TechnicalDocumentError(
|
|||||||
* @throws NullPointerException if candidate or errorMessage is null
|
* @throws NullPointerException if candidate or errorMessage is null
|
||||||
* @throws IllegalArgumentException if errorMessage is empty
|
* @throws IllegalArgumentException if errorMessage is empty
|
||||||
*/
|
*/
|
||||||
public M3TechnicalDocumentError {
|
public TechnicalDocumentError {
|
||||||
Objects.requireNonNull(candidate, "candidate must not be null");
|
Objects.requireNonNull(candidate, "candidate must not be null");
|
||||||
Objects.requireNonNull(errorMessage, "errorMessage must not be null");
|
Objects.requireNonNull(errorMessage, "errorMessage must not be null");
|
||||||
if (errorMessage.isEmpty()) {
|
if (errorMessage.isEmpty()) {
|
||||||
@@ -3,34 +3,34 @@
|
|||||||
* <p>
|
* <p>
|
||||||
* This package contains the fundamental domain entities and status models required for document processing:
|
* This package contains the fundamental domain entities and status models required for document processing:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus} — enumeration of all valid document processing states (M2-AP-001)</li>
|
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus} — enumeration of all valid document processing states</li>
|
||||||
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.RunId} — unique identifier for a batch run (M2-AP-003)</li>
|
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.RunId} — unique identifier for a batch run</li>
|
||||||
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.BatchRunContext} — technical context for a batch run (M2-AP-003)</li>
|
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.BatchRunContext} — technical context for a batch run</li>
|
||||||
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate} — discovered PDF from source folder (M3-AP-001)</li>
|
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate} — discovered PDF from source folder</li>
|
||||||
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator} — opaque locator passed from scan adapter to extraction adapter (M3-AP-001)</li>
|
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator} — opaque locator passed from scan adapter to extraction adapter</li>
|
||||||
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.PdfPageCount} — typed page count validation (M3-AP-001)</li>
|
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.PdfPageCount} — typed page count validation</li>
|
||||||
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionResult} — sealed result of PDF text extraction (M3-AP-001)</li>
|
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionResult} — sealed result of PDF text extraction</li>
|
||||||
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.M3ProcessingDecision} — sealed result of M3 pre-checks (M3-AP-001)</li>
|
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.ProcessingDecision} — sealed result of pre-checks</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* <p>
|
* <p>
|
||||||
* Additional classes introduced in M3:
|
* Additional classes:
|
||||||
* <ul>
|
* <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.PreCheckFailureReason} — enumeration of pre-check failure reasons</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.DocumentProcessingOutcome} — sealed interface for all document processing outcomes</li>
|
||||||
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.M3TechnicalDocumentError} — technical failure during extraction (M3-AP-006)</li>
|
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.TechnicalDocumentError} — technical failure during extraction</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* Implementation classes:
|
* Implementation classes:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <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.PreCheckPassed} — document passed pre-checks</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>
|
* <li>{@link de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailed} — document failed pre-check</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* M3 Document Processing Outcome Model (M3-AP-006):
|
* Document Processing Outcome Model:
|
||||||
* The complete M3 document processing pipeline results in one of three outcomes, all implementing
|
* The complete document processing pipeline results in one of three outcomes, all implementing
|
||||||
* {@link de.gecheckt.pdf.umbenenner.domain.model.M3DocumentProcessingOutcome}:
|
* {@link de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome}:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Pre-check passed: Document text extracted and validated successfully (ready for M4+)</li>
|
* <li>Pre-check passed: Document text extracted and validated successfully (ready for further processing)</li>
|
||||||
* <li>Pre-check failed: Deterministic content error (no usable text, page limit exceeded)</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>
|
* <li>Technical document error: Infrastructure or parsing failure (I/O, access, PDF parsing)</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
@@ -41,7 +41,5 @@
|
|||||||
* <li>Immutable value objects or enumerations</li>
|
* <li>Immutable value objects or enumerations</li>
|
||||||
* <li>Reusable across all layers via the Application and Adapter contracts</li>
|
* <li>Reusable across all layers via the Application and Adapter contracts</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
|
||||||
* @since M2-AP-001
|
|
||||||
*/
|
*/
|
||||||
package de.gecheckt.pdf.umbenenner.domain.model;
|
package de.gecheckt.pdf.umbenenner.domain.model;
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
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 document processing outcome types.
|
||||||
|
* <p>
|
||||||
|
* Verifies that all outcome types are properly created and validated.
|
||||||
|
*/
|
||||||
|
class DocumentProcessingOutcomeTest {
|
||||||
|
|
||||||
|
@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 testTechnicalDocumentError_ValidConstruction() {
|
||||||
|
// Act
|
||||||
|
var error = new TechnicalDocumentError(candidate, "I/O error", null);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals(candidate, error.candidate());
|
||||||
|
assertEquals("I/O error", error.errorMessage());
|
||||||
|
assertNull(error.cause());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testTechnicalDocumentError_WithCause() {
|
||||||
|
// Arrange
|
||||||
|
var cause = new RuntimeException("File not found");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var error = new TechnicalDocumentError(candidate, "I/O error", cause);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals(cause, error.cause());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testTechnicalDocumentError_WithNullCandidate_ThrowsException() {
|
||||||
|
assertThrows(NullPointerException.class,
|
||||||
|
() -> new TechnicalDocumentError(null, "Error", null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testTechnicalDocumentError_WithNullErrorMessage_ThrowsException() {
|
||||||
|
assertThrows(NullPointerException.class,
|
||||||
|
() -> new TechnicalDocumentError(candidate, null, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testTechnicalDocumentError_WithEmptyErrorMessage_ThrowsException() {
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> new TechnicalDocumentError(candidate, "", null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testTechnicalDocumentError_IsDocumentProcessingOutcome() {
|
||||||
|
// Verify type relationship
|
||||||
|
var error = new TechnicalDocumentError(candidate, "Error", null);
|
||||||
|
assertInstanceOf(DocumentProcessingOutcome.class, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPreCheckPassed_IsDocumentProcessingOutcome() {
|
||||||
|
// Verify type relationship
|
||||||
|
var extraction = new PdfExtractionSuccess("text", new PdfPageCount(1));
|
||||||
|
var passed = new PreCheckPassed(candidate, extraction);
|
||||||
|
assertInstanceOf(DocumentProcessingOutcome.class, passed);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPreCheckFailed_IsDocumentProcessingOutcome() {
|
||||||
|
// Verify type relationship
|
||||||
|
var failed = new PreCheckFailed(candidate, "Test failure reason");
|
||||||
|
assertInstanceOf(DocumentProcessingOutcome.class, failed);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAllOutcomesAreExhaustive() {
|
||||||
|
// This test verifies that the outcome types are the only implementations
|
||||||
|
var extraction = new PdfExtractionSuccess("text", new PdfPageCount(1));
|
||||||
|
|
||||||
|
DocumentProcessingOutcome[] outcomes = {
|
||||||
|
new PreCheckPassed(candidate, extraction),
|
||||||
|
new PreCheckFailed(candidate, "Deterministic content failure"),
|
||||||
|
new TechnicalDocumentError(candidate, "Technical extraction error", null)
|
||||||
|
};
|
||||||
|
|
||||||
|
for (DocumentProcessingOutcome outcome : outcomes) {
|
||||||
|
assertInstanceOf(DocumentProcessingOutcome.class, outcome);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
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