1
0

M4 AP-007 Bootstrap, sqlite.file und Startinitialisierung verdrahten

This commit is contained in:
2026-04-03 11:43:36 +02:00
parent 049aa361db
commit 1b423e8697
31 changed files with 251 additions and 165 deletions

View File

@@ -19,7 +19,8 @@
"Bash(mvn -q test)", "Bash(mvn -q test)",
"Bash(mvn -q clean test)", "Bash(mvn -q clean test)",
"Bash(git add:*)", "Bash(git add:*)",
"Bash(git commit -m ':*)" "Bash(git commit -m ':*)",
"Bash(./mvnw.cmd:*)"
] ]
} }
} }

View File

@@ -1,17 +1,19 @@
package de.gecheckt.pdf.umbenenner.adapter.in.cli; package de.gecheckt.pdf.umbenenner.adapter.in.cli;
import de.gecheckt.pdf.umbenenner.adapter.in.cli.SchedulerBatchCommand; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.Instant;
import org.junit.jupiter.api.Test;
import de.gecheckt.pdf.umbenenner.application.port.in.BatchRunOutcome; import de.gecheckt.pdf.umbenenner.application.port.in.BatchRunOutcome;
import de.gecheckt.pdf.umbenenner.application.port.in.BatchRunProcessingUseCase; import de.gecheckt.pdf.umbenenner.application.port.in.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;
import org.junit.jupiter.api.Test;
import java.time.Instant;
import static org.junit.jupiter.api.Assertions.*;
/** /**
* Unit tests for {@link SchedulerBatchCommand}. * Unit tests for {@link SchedulerBatchCommand}.
* <p> * <p>

View File

@@ -1,22 +1,23 @@
package de.gecheckt.pdf.umbenenner.adapter.out.sqlite; package de.gecheckt.pdf.umbenenner.adapter.out.sqlite;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException; import static org.junit.jupiter.api.Assertions.assertSame;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord; import static org.junit.jupiter.api.Assertions.assertThrows;
import de.gecheckt.pdf.umbenenner.application.port.out.FailureCounters; import static org.junit.jupiter.api.Assertions.assertTrue;
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttempt;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
import de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus;
import de.gecheckt.pdf.umbenenner.domain.model.RunId;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Instant; import java.time.Instant;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord;
import de.gecheckt.pdf.umbenenner.application.port.out.FailureCounters;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
import de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
/** /**
* Unit tests for {@link SqliteUnitOfWorkAdapter}. * Unit tests for {@link SqliteUnitOfWorkAdapter}.

View File

@@ -1,13 +1,13 @@
package de.gecheckt.pdf.umbenenner.application.config; package de.gecheckt.pdf.umbenenner.application.config;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/** /**
* Validates {@link StartConfiguration} before processing can begin. * Validates {@link StartConfiguration} before processing can begin.
* <p> * <p>

View File

@@ -27,7 +27,9 @@ package de.gecheckt.pdf.umbenenner.application.port.out;
*/ */
public class DocumentPersistenceException extends RuntimeException { public class DocumentPersistenceException extends RuntimeException {
/** private static final long serialVersionUID = -8362115097253107643L;
/**
* Constructs a new {@code DocumentPersistenceException} with the given message. * Constructs a new {@code DocumentPersistenceException} with the given message.
* *
* @param message human-readable description of the persistence failure * @param message human-readable description of the persistence failure

View File

@@ -1,12 +1,12 @@
package de.gecheckt.pdf.umbenenner.application.port.out; package de.gecheckt.pdf.umbenenner.application.port.out;
import java.time.Instant;
import java.util.Objects;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint; import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
import de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus; import de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator; import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
import java.time.Instant;
import java.util.Objects;
/** /**
* Application-facing representation of the document master record (Dokument-Stammsatz). * Application-facing representation of the document master record (Dokument-Stammsatz).
* <p> * <p>

View File

@@ -1,9 +1,9 @@
package de.gecheckt.pdf.umbenenner.application.port.out; package de.gecheckt.pdf.umbenenner.application.port.out;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
import java.util.Objects; import java.util.Objects;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
/** /**
* Successful outcome of a fingerprint computation. * Successful outcome of a fingerprint computation.
* <p> * <p>

View File

@@ -1,12 +1,12 @@
package de.gecheckt.pdf.umbenenner.application.port.out; package de.gecheckt.pdf.umbenenner.application.port.out;
import java.time.Instant;
import java.util.Objects;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint; import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
import de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus; import de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus;
import de.gecheckt.pdf.umbenenner.domain.model.RunId; import de.gecheckt.pdf.umbenenner.domain.model.RunId;
import java.time.Instant;
import java.util.Objects;
/** /**
* Application-facing representation of exactly one historised processing attempt * Application-facing representation of exactly one historised processing attempt
* (Versuchshistorie-Eintrag) for an identified document. * (Versuchshistorie-Eintrag) for an identified document.

View File

@@ -1,9 +1,9 @@
package de.gecheckt.pdf.umbenenner.application.port.out; package de.gecheckt.pdf.umbenenner.application.port.out;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
import java.util.List; import java.util.List;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
/** /**
* Outbound port for writing and reading the processing attempt history * Outbound port for writing and reading the processing attempt history
* (Versuchshistorie). * (Versuchshistorie).

View File

@@ -1,9 +1,9 @@
package de.gecheckt.pdf.umbenenner.application.port.out; package de.gecheckt.pdf.umbenenner.application.port.out;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
import java.util.List; import java.util.List;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
/** /**
* Outbound port for loading PDF document candidates from the source folder. * Outbound port for loading PDF document candidates from the source folder.
* <p> * <p>

View File

@@ -1,7 +1,5 @@
package de.gecheckt.pdf.umbenenner.application.port.out; package de.gecheckt.pdf.umbenenner.application.port.out;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
import java.util.function.Consumer; import java.util.function.Consumer;
/** /**

View File

@@ -1,17 +1,17 @@
package de.gecheckt.pdf.umbenenner.application.service; package de.gecheckt.pdf.umbenenner.application.service;
import java.util.Objects;
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration; import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome; import de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome;
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailed;
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailureReason;
import de.gecheckt.pdf.umbenenner.domain.model.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;
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionTechnicalError; import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionTechnicalError;
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailed;
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailureReason;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate; import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
import de.gecheckt.pdf.umbenenner.domain.model.TechnicalDocumentError;
import java.util.Objects;
/** /**
* Orchestrates document processing pipeline: extraction → pre-checks → outcome classification. * Orchestrates document processing pipeline: extraction → pre-checks → outcome classification.

View File

@@ -1,5 +1,11 @@
package de.gecheckt.pdf.umbenenner.application.service; package de.gecheckt.pdf.umbenenner.application.service;
import java.time.Instant;
import java.util.Objects;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentKnownProcessable; import de.gecheckt.pdf.umbenenner.application.port.out.DocumentKnownProcessable;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException; import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord; import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord;
@@ -22,12 +28,6 @@ import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator; import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
import de.gecheckt.pdf.umbenenner.domain.model.TechnicalDocumentError; import de.gecheckt.pdf.umbenenner.domain.model.TechnicalDocumentError;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.time.Instant;
import java.util.Objects;
/** /**
* Application-level service that implements the M4 per-document processing logic. * Application-level service that implements the M4 per-document processing logic.
* <p> * <p>

View File

@@ -1,15 +1,15 @@
package de.gecheckt.pdf.umbenenner.application.service; package de.gecheckt.pdf.umbenenner.application.service;
import java.util.Objects;
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration; import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome; import de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome;
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailureReason;
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailed;
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckPassed;
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess; import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess;
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailed;
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailureReason;
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckPassed;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate; import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
import java.util.Objects;
/** /**
* Evaluates whether a successfully extracted PDF passes pre-checks. * Evaluates whether a successfully extracted PDF passes pre-checks.
* <p> * <p>

View File

@@ -1,5 +1,12 @@
package de.gecheckt.pdf.umbenenner.application.usecase; package de.gecheckt.pdf.umbenenner.application.usecase;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration; import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
import de.gecheckt.pdf.umbenenner.application.port.in.BatchRunOutcome; import de.gecheckt.pdf.umbenenner.application.port.in.BatchRunOutcome;
import de.gecheckt.pdf.umbenenner.application.port.in.BatchRunProcessingUseCase; import de.gecheckt.pdf.umbenenner.application.port.in.BatchRunProcessingUseCase;
@@ -20,13 +27,6 @@ import de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome;
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionResult; import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionResult;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate; import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
/** /**
* Batch processing implementation of {@link BatchRunProcessingUseCase}. * Batch processing implementation of {@link BatchRunProcessingUseCase}.
* <p> * <p>

View File

@@ -1,13 +1,15 @@
package de.gecheckt.pdf.umbenenner.application.config; package de.gecheckt.pdf.umbenenner.application.config;
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import org.junit.jupiter.api.io.TempDir; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.net.URI; import java.net.URI;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
/** /**
* Unit tests for {@link StartConfigurationValidator}. * Unit tests for {@link StartConfigurationValidator}.

View File

@@ -1,8 +1,14 @@
package de.gecheckt.pdf.umbenenner.application.port.in; package de.gecheckt.pdf.umbenenner.application.port.in;
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
/** /**
* Unit tests for {@link BatchRunOutcome} enumeration. * Unit tests for {@link BatchRunOutcome} enumeration.

View File

@@ -1,27 +1,31 @@
package de.gecheckt.pdf.umbenenner.application.service; package de.gecheckt.pdf.umbenenner.application.service;
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration; import static org.junit.jupiter.api.Assertions.assertEquals;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome; import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailed; import static org.junit.jupiter.api.Assertions.assertNotNull;
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailureReason; import static org.junit.jupiter.api.Assertions.assertNull;
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckPassed; import static org.junit.jupiter.api.Assertions.assertThrows;
import de.gecheckt.pdf.umbenenner.domain.model.TechnicalDocumentError;
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionContentError;
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess;
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionTechnicalError;
import de.gecheckt.pdf.umbenenner.domain.model.PdfPageCount;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.net.URI; import java.net.URI;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome;
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionContentError;
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess;
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionTechnicalError;
import de.gecheckt.pdf.umbenenner.domain.model.PdfPageCount;
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailed;
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailureReason;
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckPassed;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
import de.gecheckt.pdf.umbenenner.domain.model.TechnicalDocumentError;
/** /**
* Tests for {@link DocumentProcessingService}. * Tests for {@link DocumentProcessingService}.

View File

@@ -1,5 +1,20 @@
package de.gecheckt.pdf.umbenenner.application.service; package de.gecheckt.pdf.umbenenner.application.service;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentKnownProcessable; import de.gecheckt.pdf.umbenenner.application.port.out.DocumentKnownProcessable;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException; import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord; import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord;
@@ -27,16 +42,6 @@ import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator; import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
import de.gecheckt.pdf.umbenenner.domain.model.TechnicalDocumentError; import de.gecheckt.pdf.umbenenner.domain.model.TechnicalDocumentError;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import static org.junit.jupiter.api.Assertions.*;
/** /**
* Unit tests for {@link M4DocumentProcessor}. * Unit tests for {@link M4DocumentProcessor}.
* <p> * <p>

View File

@@ -1,23 +1,27 @@
package de.gecheckt.pdf.umbenenner.application.service; package de.gecheckt.pdf.umbenenner.application.service;
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration; import static org.junit.jupiter.api.Assertions.assertEquals;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome; import static org.junit.jupiter.api.Assertions.assertNotNull;
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailed; import static org.junit.jupiter.api.Assertions.assertSame;
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailureReason; import static org.junit.jupiter.api.Assertions.assertThrows;
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckPassed; import static org.junit.jupiter.api.Assertions.assertTrue;
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess;
import de.gecheckt.pdf.umbenenner.domain.model.PdfPageCount;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.net.URI; import java.net.URI;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentProcessingOutcome;
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess;
import de.gecheckt.pdf.umbenenner.domain.model.PdfPageCount;
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailed;
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckFailureReason;
import de.gecheckt.pdf.umbenenner.domain.model.PreCheckPassed;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
/** /**
* Tests for {@link PreCheckEvaluator}. * Tests for {@link PreCheckEvaluator}.

View File

@@ -1,5 +1,21 @@
package de.gecheckt.pdf.umbenenner.application.usecase; package de.gecheckt.pdf.umbenenner.application.usecase;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration; import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
import de.gecheckt.pdf.umbenenner.application.port.in.BatchRunOutcome; import de.gecheckt.pdf.umbenenner.application.port.in.BatchRunOutcome;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord; import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord;
@@ -30,20 +46,6 @@ import de.gecheckt.pdf.umbenenner.domain.model.RunId;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate; import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator; import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import static org.junit.jupiter.api.Assertions.*;
/** /**
* Tests for {@link DefaultBatchRunProcessingUseCase}. * Tests for {@link DefaultBatchRunProcessingUseCase}.
* <p> * <p>

View File

@@ -16,6 +16,7 @@ import de.gecheckt.pdf.umbenenner.adapter.out.pdfextraction.PdfTextExtractionPor
import de.gecheckt.pdf.umbenenner.adapter.out.sourcedocument.SourceDocumentCandidatesPortAdapter; import de.gecheckt.pdf.umbenenner.adapter.out.sourcedocument.SourceDocumentCandidatesPortAdapter;
import de.gecheckt.pdf.umbenenner.adapter.out.sqlite.SqliteDocumentRecordRepositoryAdapter; import de.gecheckt.pdf.umbenenner.adapter.out.sqlite.SqliteDocumentRecordRepositoryAdapter;
import de.gecheckt.pdf.umbenenner.adapter.out.sqlite.SqliteProcessingAttemptRepositoryAdapter; import de.gecheckt.pdf.umbenenner.adapter.out.sqlite.SqliteProcessingAttemptRepositoryAdapter;
import de.gecheckt.pdf.umbenenner.adapter.out.sqlite.SqliteSchemaInitializationAdapter;
import de.gecheckt.pdf.umbenenner.adapter.out.sqlite.SqliteUnitOfWorkAdapter; import de.gecheckt.pdf.umbenenner.adapter.out.sqlite.SqliteUnitOfWorkAdapter;
import de.gecheckt.pdf.umbenenner.application.config.InvalidStartConfigurationException; import de.gecheckt.pdf.umbenenner.application.config.InvalidStartConfigurationException;
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration; import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
@@ -26,6 +27,7 @@ import de.gecheckt.pdf.umbenenner.application.port.out.ConfigurationPort;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException; import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecordRepository; import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecordRepository;
import de.gecheckt.pdf.umbenenner.application.port.out.FingerprintPort; import de.gecheckt.pdf.umbenenner.application.port.out.FingerprintPort;
import de.gecheckt.pdf.umbenenner.application.port.out.PersistenceSchemaInitializationPort;
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttemptRepository; import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttemptRepository;
import de.gecheckt.pdf.umbenenner.application.port.out.RunLockPort; import de.gecheckt.pdf.umbenenner.application.port.out.RunLockPort;
import de.gecheckt.pdf.umbenenner.application.port.out.UnitOfWorkPort; import de.gecheckt.pdf.umbenenner.application.port.out.UnitOfWorkPort;
@@ -40,6 +42,7 @@ import de.gecheckt.pdf.umbenenner.domain.model.RunId;
* Responsibilities: * Responsibilities:
* <ol> * <ol>
* <li>Load and validate the startup configuration.</li> * <li>Load and validate the startup configuration.</li>
* <li>Initialise the SQLite persistence schema (M4-AP-007).</li>
* <li>Resolve the run-lock file path (with default fallback).</li> * <li>Resolve the run-lock file path (with default fallback).</li>
* <li>Create and wire all ports and adapters via configured factories.</li> * <li>Create and wire all ports and adapters via configured factories.</li>
* <li>Start the CLI adapter and execute the batch use case.</li> * <li>Start the CLI adapter and execute the batch use case.</li>
@@ -56,19 +59,22 @@ import de.gecheckt.pdf.umbenenner.domain.model.RunId;
* during the run.</li> * during the run.</li>
* </ul> * </ul>
* *
* <h2>M4 wiring (AP-006)</h2> * <h2>M4 wiring (AP-006 / AP-007)</h2>
* <p> * <p>
* The production constructor wires the following M4 adapters via the UseCaseFactory: * The production constructor wires the following M4 adapters:
* <ul> * <ul>
* <li>{@link SqliteSchemaInitializationAdapter} — SQLite schema DDL at startup (AP-007).</li>
* <li>{@link Sha256FingerprintAdapter} — SHA-256 content fingerprinting.</li> * <li>{@link Sha256FingerprintAdapter} — SHA-256 content fingerprinting.</li>
* <li>{@link SqliteDocumentRecordRepositoryAdapter} — document master record CRUD.</li> * <li>{@link SqliteDocumentRecordRepositoryAdapter} — document master record CRUD.</li>
* <li>{@link SqliteProcessingAttemptRepositoryAdapter} — attempt history CRUD.</li> * <li>{@link SqliteProcessingAttemptRepositoryAdapter} — attempt history CRUD.</li>
* <li>{@link SqliteUnitOfWorkAdapter} — atomic persistence operations.</li> * <li>{@link SqliteUnitOfWorkAdapter} — atomic persistence operations.</li>
* </ul> * </ul>
* <p> * <p>
* Schema initialisation is AP-007 responsibility, not performed in AP-006. * Schema initialisation is performed once in {@link #run()} before the batch loop starts
* (AP-007). A {@link DocumentPersistenceException} during schema initialisation is treated
* as a hard startup failure and results in exit code 1.
* *
* @since M2 (extended in M4-AP-006) * @since M2 (extended in M4-AP-006, M4-AP-007)
*/ */
public class BootstrapRunner { public class BootstrapRunner {
@@ -134,12 +140,14 @@ public class BootstrapRunner {
* <li>{@link SourceDocumentCandidatesPortAdapter} for PDF candidate discovery.</li> * <li>{@link SourceDocumentCandidatesPortAdapter} for PDF candidate discovery.</li>
* <li>{@link PdfTextExtractionPortAdapter} for PDFBox-based text and page count extraction.</li> * <li>{@link PdfTextExtractionPortAdapter} for PDFBox-based text and page count extraction.</li>
* <li>{@link Sha256FingerprintAdapter} for SHA-256 content fingerprinting.</li> * <li>{@link Sha256FingerprintAdapter} for SHA-256 content fingerprinting.</li>
* <li>{@link SqliteSchemaInitializationAdapter} for SQLite schema DDL at startup (AP-007).</li>
* <li>{@link SqliteDocumentRecordRepositoryAdapter} for document master record CRUD.</li> * <li>{@link SqliteDocumentRecordRepositoryAdapter} for document master record CRUD.</li>
* <li>{@link SqliteProcessingAttemptRepositoryAdapter} for attempt history CRUD.</li> * <li>{@link SqliteProcessingAttemptRepositoryAdapter} for attempt history CRUD.</li>
* <li>{@link SqliteUnitOfWorkAdapter} for atomic persistence operations.</li> * <li>{@link SqliteUnitOfWorkAdapter} for atomic persistence operations.</li>
* </ul> * </ul>
* <p> * <p>
* Schema initialisation is AP-007 responsibility and is NOT performed here. * Schema initialisation is performed explicitly in {@link #run()} before the batch loop
* begins (AP-007). Failure during initialisation aborts the run with exit code 1.
*/ */
public BootstrapRunner() { public BootstrapRunner() {
this.configPortFactory = PropertiesConfigurationPortAdapter::new; this.configPortFactory = PropertiesConfigurationPortAdapter::new;
@@ -191,13 +199,27 @@ public class BootstrapRunner {
/** /**
* Runs the application startup sequence. * Runs the application startup sequence.
* <p> * <p>
* M4 additions: * M4 startup flow (AP-007):
* <ul> * <ol>
* <li>Derives the SQLite JDBC URL from the configured {@code sqlite.file} path.</li> * <li>Load configuration via {@link ConfigurationPort}.</li>
* <li>Creates the M4-aware use case via the {@link UseCaseFactory}, which wires persistence ports.</li> * <li>Validate the configuration via {@link StartConfigurationValidator}; validation
* </ul> * includes checking that the {@code sqlite.file} parent directory exists or is
* technically creatable.</li>
* <li>Initialise the SQLite persistence schema explicitly via
* {@link PersistenceSchemaInitializationPort}. This step happens once, before the
* batch document loop begins. A {@link DocumentPersistenceException} here is a hard
* startup failure and causes exit code 1.</li>
* <li>Resolve the run-lock file path, apply default if not configured.</li>
* <li>Create the batch use case with all M4 adapters wired.</li>
* <li>Execute the CLI command and map the outcome to an exit code.</li>
* </ol>
* <p>
* Document-level failures during the batch loop (step 6) are not startup failures and
* do not change the exit code as long as the run itself completes without a hard
* infrastructure error.
* *
* @return exit code: 0 for success, 1 for invalid configuration or unexpected bootstrap failure * @return exit code: 0 for a technically completed run, 1 for any hard startup or
* bootstrap failure (configuration invalid, schema init failed, etc.)
*/ */
public int run() { public int run() {
LOG.info("Bootstrap flow started."); LOG.info("Bootstrap flow started.");
@@ -208,11 +230,18 @@ public class BootstrapRunner {
// Step 2: Load configuration // Step 2: Load configuration
var config = configPort.loadConfiguration(); var config = configPort.loadConfiguration();
// Step 3: Validate configuration // Step 3: Validate configuration.
// Includes checking that sqlite.file parent directory exists or is creatable.
StartConfigurationValidator validator = validatorFactory.create(); StartConfigurationValidator validator = validatorFactory.create();
validator.validate(config); validator.validate(config);
// Step 4: Resolve lock file path apply default if not configured // Step 4 (M4-AP-007): Initialise SQLite persistence schema before the batch loop.
// Must happen once at startup; failure here is a hard bootstrap error → exit code 1.
String jdbcUrl = buildJdbcUrl(config);
PersistenceSchemaInitializationPort schemaInitPort = new SqliteSchemaInitializationAdapter(jdbcUrl);
schemaInitPort.initializeSchema();
// Step 5: Resolve lock file path apply default if not configured
Path lockFilePath = config.runtimeLockFile(); Path lockFilePath = config.runtimeLockFile();
if (lockFilePath == null || lockFilePath.toString().isBlank()) { if (lockFilePath == null || lockFilePath.toString().isBlank()) {
lockFilePath = Paths.get("pdf-umbenenner.lock"); lockFilePath = Paths.get("pdf-umbenenner.lock");
@@ -221,21 +250,20 @@ public class BootstrapRunner {
} }
RunLockPort runLockPort = runLockPortFactory.create(lockFilePath); RunLockPort runLockPort = runLockPortFactory.create(lockFilePath);
// Step 5: Create the batch run context // Step 6: Create the batch 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());
LOG.info("Batch run started. RunId: {}", runId); LOG.info("Batch run started. RunId: {}", runId);
// Step 6: Create the use case with the validated config and run lock. // Step 7: Create the use case with the validated config and run lock.
// 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.
// Adapters (source document port, PDF extraction port, M4 ports) are wired by the factory. // Adapters (source document port, PDF extraction port, M4 ports) are wired by the factory.
// Schema initialization is AP-007 responsibility, not performed in AP-006.
BatchRunProcessingUseCase useCase = useCaseFactory.create(config, runLockPort); BatchRunProcessingUseCase useCase = useCaseFactory.create(config, runLockPort);
// Step 7: Create the CLI command adapter with the use case // Step 8: Create the CLI command adapter with the use case
SchedulerBatchCommand command = commandFactory.create(useCase); SchedulerBatchCommand command = commandFactory.create(useCase);
// Step 8: Execute the command with the run context and handle the outcome // Step 9: Execute the command with the run context and handle the outcome
BatchRunOutcome outcome = command.run(runContext); BatchRunOutcome outcome = command.run(runContext);
// Mark run as completed // Mark run as completed

View File

@@ -25,5 +25,9 @@
* AP-005: CLI adapter and complete M2 object graph wiring. * AP-005: CLI adapter and complete M2 object graph wiring.
* <p> * <p>
* AP-006: Wires FilesystemRunLockPortAdapter (adapter-out) from validated config; retired temporary no-op lock. * AP-006: Wires FilesystemRunLockPortAdapter (adapter-out) from validated config; retired temporary no-op lock.
* <p>
* M4-AP-007: Schema initialisation wired — {@code SqliteSchemaInitializationAdapter.initializeSchema()} is
* called explicitly in {@code BootstrapRunner.run()} after configuration validation and before the batch
* document loop. A {@code DocumentPersistenceException} during initialisation aborts the run with exit code 1.
*/ */
package de.gecheckt.pdf.umbenenner.bootstrap; package de.gecheckt.pdf.umbenenner.bootstrap;

View File

@@ -1,5 +1,18 @@
package de.gecheckt.pdf.umbenenner.bootstrap; package de.gecheckt.pdf.umbenenner.bootstrap;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import de.gecheckt.pdf.umbenenner.adapter.in.cli.SchedulerBatchCommand; import de.gecheckt.pdf.umbenenner.adapter.in.cli.SchedulerBatchCommand;
import de.gecheckt.pdf.umbenenner.application.config.InvalidStartConfigurationException; import de.gecheckt.pdf.umbenenner.application.config.InvalidStartConfigurationException;
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration; import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
@@ -10,17 +23,6 @@ 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.domain.model.BatchRunContext; import de.gecheckt.pdf.umbenenner.domain.model.BatchRunContext;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.jupiter.api.Assertions.*;
/** /**
* Unit tests for {@link BootstrapRunner}. * Unit tests for {@link BootstrapRunner}.
* <p> * <p>

View File

@@ -1,7 +1,8 @@
package de.gecheckt.pdf.umbenenner.bootstrap; package de.gecheckt.pdf.umbenenner.bootstrap;
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.io.TempDir; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.File; import java.io.File;
import java.nio.file.Files; import java.nio.file.Files;
@@ -10,7 +11,8 @@ import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
/** /**
* AP-008: Executable JAR smoke tests for M1 target state verification. * AP-008: Executable JAR smoke tests for M1 target state verification.

View File

@@ -1,10 +1,15 @@
package de.gecheckt.pdf.umbenenner.domain.model; package de.gecheckt.pdf.umbenenner.domain.model;
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.Instant; import java.time.Instant;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
/** /**
* Unit tests for {@link BatchRunContext}. * Unit tests for {@link BatchRunContext}.

View File

@@ -1,13 +1,16 @@
package de.gecheckt.pdf.umbenenner.domain.model; package de.gecheckt.pdf.umbenenner.domain.model;
import org.junit.jupiter.api.BeforeEach; import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import org.junit.jupiter.api.io.TempDir; import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
/** /**
* Tests for document processing outcome types. * Tests for document processing outcome types.

View File

@@ -1,7 +1,12 @@
package de.gecheckt.pdf.umbenenner.domain.model; package de.gecheckt.pdf.umbenenner.domain.model;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/** /**
* Tests for PdfPageCount record. * Tests for PdfPageCount record.

View File

@@ -1,8 +1,13 @@
package de.gecheckt.pdf.umbenenner.domain.model; package de.gecheckt.pdf.umbenenner.domain.model;
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
/** /**
* Tests for {@link PreCheckFailureReason}. * Tests for {@link PreCheckFailureReason}.

View File

@@ -1,8 +1,10 @@
package de.gecheckt.pdf.umbenenner.domain.model; package de.gecheckt.pdf.umbenenner.domain.model;
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
/** /**
* Unit tests for {@link ProcessingStatus} enumeration. * Unit tests for {@link ProcessingStatus} enumeration.

View File

@@ -1,8 +1,11 @@
package de.gecheckt.pdf.umbenenner.domain.model; package de.gecheckt.pdf.umbenenner.domain.model;
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test;
/** /**
* Unit tests for {@link RunId} value object. * Unit tests for {@link RunId} value object.