M8 Abschlussdokumentation und Betriebsdoku final geschärft
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
# Befundliste – Integrierte Gesamtprüfung des Endstands
|
# Befundliste – Integrierte Gesamtprüfung und Freigabe des Endstands
|
||||||
|
|
||||||
**Erstellt:** 2026-04-08
|
**Erstellt:** 2026-04-08
|
||||||
|
**Aktualisiert:** 2026-04-08 (Naming-Convention-Bereinigung B1 abgeschlossen, finale Freigabe)
|
||||||
**Grundlage:** Vollständiger Maven-Reactor-Build, Unit-Tests, E2E-Tests, Integrationstests (Smoke),
|
**Grundlage:** Vollständiger Maven-Reactor-Build, Unit-Tests, E2E-Tests, Integrationstests (Smoke),
|
||||||
PIT-Mutationsanalyse, Code-Review gegen verbindliche Spezifikationen (technik-und-architektur.md,
|
PIT-Mutationsanalyse, Code-Review gegen verbindliche Spezifikationen (technik-und-architektur.md,
|
||||||
fachliche-anforderungen.md, CLAUDE.md)
|
fachliche-anforderungen.md, CLAUDE.md)
|
||||||
@@ -20,7 +21,7 @@ fachliche-anforderungen.md, CLAUDE.md)
|
|||||||
| Hexagonale Architektur – Port-Verträge (kein Path/NIO/JDBC) | ja | GRÜN |
|
| Hexagonale Architektur – Port-Verträge (kein Path/NIO/JDBC) | ja | GRÜN |
|
||||||
| Hexagonale Architektur – keine Adapter-zu-Adapter-Abhängigkeiten | ja | GRÜN |
|
| Hexagonale Architektur – keine Adapter-zu-Adapter-Abhängigkeiten | ja | GRÜN |
|
||||||
| Statusmodell (8 Werte, Semantik laut CLAUDE.md) | ja | GRÜN |
|
| Statusmodell (8 Werte, Semantik laut CLAUDE.md) | ja | GRÜN |
|
||||||
| Naming-Convention-Regel (kein M1–M8, kein AP-xxx im Code) | ja | OFFEN (nicht blockierend) |
|
| Naming-Convention-Regel (kein M1–M8, kein AP-xxx im Code) | ja | GRÜN |
|
||||||
| Logging-Sensibilitätsregel (log.ai.sensitive) | ja | GRÜN |
|
| Logging-Sensibilitätsregel (log.ai.sensitive) | ja | GRÜN |
|
||||||
| Exit-Code-Semantik (0 / 1) | ja | GRÜN |
|
| Exit-Code-Semantik (0 / 1) | ja | GRÜN |
|
||||||
| Konfigurationsbeispiele (Pflicht- und Optionalparameter) | ja | GRÜN |
|
| Konfigurationsbeispiele (Pflicht- und Optionalparameter) | ja | GRÜN |
|
||||||
@@ -111,55 +112,22 @@ Alle geforderten Kernszenarien aus der E2E-Testbasis sind abgedeckt und grün:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Offene Punkte
|
## Abgeschlossene Punkte
|
||||||
|
|
||||||
### Nicht blockierend
|
### B1 – Naming-Convention-Verletzungen in Code, Tests und Konfiguration (CLAUDE.md § Naming-Regel)
|
||||||
|
|
||||||
#### B1 – Naming-Convention-Verletzungen in Code, Tests und Konfiguration (CLAUDE.md § Naming-Regel)
|
|
||||||
|
|
||||||
**Themenbereich:** Dokumentation / Codequalität
|
**Themenbereich:** Dokumentation / Codequalität
|
||||||
**Norm:** CLAUDE.md verbietet explizit Meilenstein- (M1–M8) und Arbeitspaket-Bezeichner (AP-xxx)
|
**Norm:** CLAUDE.md verbietet explizit Meilenstein- (M1–M8) und Arbeitspaket-Bezeichner (AP-xxx)
|
||||||
in Implementierungen, Kommentaren und JavaDoc.
|
in Implementierungen, Kommentaren und JavaDoc.
|
||||||
**Befund:** 43 Treffer in `.java`-Dateien (21 in Produktionscode, 22 in Testcode) sowie
|
**Status:** **BEHOBEN** – alle 43 Treffer in `.java`-Dateien sowie der Kommentarheader in
|
||||||
1 Treffer in `config/application.properties`.
|
`config/application.properties` wurden durch zeitlose technische Formulierungen ersetzt.
|
||||||
|
|
||||||
Betroffene Dateien (Auswahl Produktionscode):
|
|
||||||
|
|
||||||
| Datei | Verstoß |
|
|
||||||
|---|---|
|
|
||||||
| `domain/model/BatchRunContext.java` | `@since M2-AP-003` |
|
|
||||||
| `domain/model/DocumentFingerprint.java` | `@since M4-AP-001`, `Identification semantics (M4)` |
|
|
||||||
| `domain/model/PdfExtractionResult.java` | `@since M3-AP-001` |
|
|
||||||
| `domain/model/SourceDocumentCandidate.java` | `@since M3-AP-001`, `AP-004` in Parameterbeschreibung |
|
|
||||||
| `domain/model/SourceDocumentLocator.java` | `@since M3-AP-001` |
|
|
||||||
| `adapter/out/lock/FilesystemRunLockPortAdapter.java` | `AP-006 Implementation:` in JavaDoc |
|
|
||||||
| `adapter/out/pdfextraction/PdfTextExtractionPortAdapter.java` | `AP-003:` in Inline-Kommentaren |
|
|
||||||
| `adapter/out/sourcedocument/SourceDocumentCandidatesPortAdapter.java` | `AP-002 Implementation`, `@since M3-AP-002`, `AP-003`, `AP-004` |
|
|
||||||
| `config/application.properties` | Kommentarheader `# PDF Umbenenner Configuration for AP-006 Testing` |
|
|
||||||
|
|
||||||
Betroffene Dateien (Auswahl Testcode):
|
|
||||||
|
|
||||||
| Datei | Verstoß |
|
|
||||||
|---|---|
|
|
||||||
| `adapter/out/bootstrap/validation/StartConfigurationValidatorTest.java` | `M3/AP-007` |
|
|
||||||
| `adapter/out/fingerprint/Sha256FingerprintAdapterTest.java` | `@since M4-AP-002` |
|
|
||||||
| `adapter/out/pdfextraction/PdfTextExtractionPortAdapterTest.java` | `M3-AP-003`, `AP-003`, `AP-004` |
|
|
||||||
| `adapter/out/sourcedocument/SourceDocumentCandidatesPortAdapterTest.java` | `M3-AP-002`, `AP-004` |
|
|
||||||
| `adapter/out/sqlite/SqliteUnitOfWorkAdapterTest.java` | `@since M4-AP-006` |
|
|
||||||
| `application/service/DefaultRetryDecisionEvaluatorTest.java` | `M4-M6` in Kommentar |
|
|
||||||
| `application/service/DocumentProcessingCoordinatorTest.java` | `M5`, `M6` in Kommentaren |
|
|
||||||
| `application/service/ProcessingOutcomeTransitionTest.java` | `M4-M6` in Kommentar |
|
|
||||||
| `application/usecase/BatchRunProcessingUseCaseTest.java` | `M7` in Kommentar |
|
|
||||||
| `bootstrap/ExecutableJarSmokeTestIT.java` | `AP-008`, `M1` in JavaDoc |
|
|
||||||
|
|
||||||
**Bewertung:** Rein kosmetisch/dokumentarisch. Kein Einfluss auf Funktionalität, Build
|
|
||||||
oder Testergebnis. Betrifft ausschließlich Kommentare und JavaDoc-Annotationen.
|
|
||||||
**Empfehlung für AP-009:** Bezeichner in betroffenen Dateien durch zeitlose technische
|
|
||||||
Formulierungen ersetzen (z. B. `@since M4-AP-001` → entfernen oder in neutrales
|
|
||||||
`@since 1.0` umwandeln; Inline-Kommentare sachlich formulieren).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Offene Punkte
|
||||||
|
|
||||||
|
### Nicht blockierend
|
||||||
|
|
||||||
#### B2 – StartConfiguration in Application-Schicht enthält java.nio.file.Path (Architektur-Grenzfall)
|
#### B2 – StartConfiguration in Application-Schicht enthält java.nio.file.Path (Architektur-Grenzfall)
|
||||||
|
|
||||||
**Themenbereich:** Architektur
|
**Themenbereich:** Architektur
|
||||||
@@ -175,9 +143,8 @@ Die Port-Verträge selbst sind sauber (keine Path-Typen in Port-Interfaces).
|
|||||||
**Bewertung:** Grenzfall. `Path` ist kein fachliches Objekt, aber auch kein schwerer
|
**Bewertung:** Grenzfall. `Path` ist kein fachliches Objekt, aber auch kein schwerer
|
||||||
Architekturverstoß in diesem Kontext. Die Alternative (String-Repräsentation und Auflösung
|
Architekturverstoß in diesem Kontext. Die Alternative (String-Repräsentation und Auflösung
|
||||||
im Adapter) hätte keinen Mehrwert für das Betriebsmodell.
|
im Adapter) hätte keinen Mehrwert für das Betriebsmodell.
|
||||||
**Empfehlung für AP-009:** Auf Wunsch im Rahmen von AP-009 prüfen, ob das Verschieben von
|
**Entscheidung:** Kein Handlungsbedarf. Das Verschieben von `StartConfiguration` in das
|
||||||
`StartConfiguration` in das Bootstrap-Modul sinnvoller wäre. Keine Pflicht, da kein
|
Bootstrap-Modul wäre eine Option, ist aber keine Pflicht, da kein funktionaler Defekt vorliegt.
|
||||||
funktionaler Defekt vorliegt.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -188,22 +155,23 @@ funktionaler Defekt vorliegt.
|
|||||||
Hauptkategorie: `VoidMethodCallMutator` (2 Überlebende, 2 ohne Coverage).
|
Hauptkategorie: `VoidMethodCallMutator` (2 Überlebende, 2 ohne Coverage).
|
||||||
**Bewertung:** Betrifft vor allem Logging-Calls und nicht-kritische Hilfsmethoden.
|
**Bewertung:** Betrifft vor allem Logging-Calls und nicht-kritische Hilfsmethoden.
|
||||||
Keine funktional tragenden Entscheidungspfade betroffen.
|
Keine funktional tragenden Entscheidungspfade betroffen.
|
||||||
**Empfehlung:** Kein AP-009-Handlungsbedarf; wurde bereits in AP-007 auf akzeptablem
|
**Entscheidung:** Kein Handlungsbedarf. Betrifft vor allem Logging-Calls und nicht-kritische
|
||||||
Niveau konsolidiert.
|
Hilfsmethoden. Wurde auf akzeptablem Niveau konsolidiert.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Zusammenfassung
|
## Zusammenfassung und Freigabe
|
||||||
|
|
||||||
| Klassifikation | Anzahl | Beschreibung |
|
| Klassifikation | Anzahl | Beschreibung |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Release-Blocker | **0** | – |
|
| Release-Blocker | **0** | – |
|
||||||
| Nicht blockierend | **3** | B1 Naming, B2 Path-Grenzfall, B3 PIT-Bootstrap |
|
| Abgeschlossen (war nicht blockierend) | **1** | B1 Naming-Convention-Bereinigung |
|
||||||
|
| Nicht blockierend, kein Handlungsbedarf | **2** | B2 Path-Grenzfall, B3 PIT-Bootstrap |
|
||||||
|
|
||||||
**Der Endstand ist produktionsbereit.** Alle fachlichen, technischen und architekturellen
|
**Freigabeentscheidung: Der Endstand ist produktionsbereit und freigegeben.**
|
||||||
Kernanforderungen sind umgesetzt und durch automatisierte Tests abgesichert. Der Maven-Build
|
|
||||||
ist fehlerfrei. Die identifizierten offenen Punkte sind ausschließlich nicht blockierend.
|
|
||||||
|
|
||||||
Falls AP-009 durchgeführt wird, sollte der Fokus auf **B1** (Naming-Convention-Bereinigung)
|
Alle fachlichen, technischen und architekturellen Kernanforderungen aus den verbindlichen
|
||||||
liegen, da dieser Punkt die einzige verbindliche CLAUDE.md-Regel betrifft, die noch nicht
|
Spezifikationen (technik-und-architektur.md, fachliche-anforderungen.md, CLAUDE.md) sind
|
||||||
vollständig eingehalten wird.
|
vollständig umgesetzt und durch automatisierte Tests abgesichert. Der Maven-Build ist fehlerfrei.
|
||||||
|
Die CLAUDE.md-Naming-Convention-Regel (kein M1–M8, kein AP-xxx im Produktions- oder Testcode)
|
||||||
|
ist vollständig eingehalten. Keine bekannten spezifikationsrelevanten Blocker sind offen.
|
||||||
|
|||||||
@@ -35,8 +35,13 @@ Empfohlene Startsequenz für den Windows Task Scheduler:
|
|||||||
|
|
||||||
1. Aktion: Programm/Skript starten
|
1. Aktion: Programm/Skript starten
|
||||||
2. Programm: `java`
|
2. Programm: `java`
|
||||||
3. Argumente: `-jar pdf-umbenenner-bootstrap-0.0.1-SNAPSHOT.jar`
|
3. Argumente: `-jar C:\Pfad\zur\Installation\pdf-umbenenner-bootstrap\target\pdf-umbenenner-bootstrap-0.0.1-SNAPSHOT.jar`
|
||||||
4. Starten in: Verzeichnis mit `config/application.properties` und `config/prompts/`
|
4. Starten in: `C:\Pfad\zur\Installation` (muss das Verzeichnis mit `config\application.properties` und `config\prompts\` enthalten)
|
||||||
|
|
||||||
|
> **Hinweis:** Das „Starten in"-Verzeichnis ist das Arbeitsverzeichnis der Anwendung.
|
||||||
|
> Die Konfigurationsdatei `config/application.properties` sowie das Prompt-Verzeichnis
|
||||||
|
> `config/prompts/` müssen relativ zu diesem Verzeichnis erreichbar sein. Der JAR-Pfad
|
||||||
|
> in den Argumenten muss absolut oder relativ zum Starten-in-Verzeichnis korrekt angegeben sein.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -126,13 +131,21 @@ Das Suffix zählt nicht zu den 20 Zeichen des Basistitels.
|
|||||||
|
|
||||||
### Dokumentstatus
|
### Dokumentstatus
|
||||||
|
|
||||||
| Status | Bedeutung |
|
Die folgende Tabelle beschreibt das vollständige Statusmodell, das in der SQLite-Datenbank
|
||||||
|---------------------------|-----------|
|
gespeichert wird.
|
||||||
| `SUCCESS` | Erfolgreich verarbeitet und kopiert |
|
|
||||||
| `FAILED_RETRYABLE` | Fehlgeschlagen, erneuter Versuch in späterem Lauf möglich |
|
| Status | Bedeutung |
|
||||||
| `FAILED_FINAL` | Terminal fehlgeschlagen, wird nicht erneut verarbeitet |
|
|-----------------------------|-----------|
|
||||||
|
| `READY_FOR_AI` | Verarbeitbar, KI-Pfad noch nicht durchlaufen |
|
||||||
|
| `PROPOSAL_READY` | KI-Benennungsvorschlag liegt vor, Zielkopie noch nicht geschrieben |
|
||||||
|
| `SUCCESS` | Erfolgreich verarbeitet und kopiert (terminaler Endzustand) |
|
||||||
|
| `FAILED_RETRYABLE` | Fehlgeschlagen, erneuter Versuch in späterem Lauf möglich |
|
||||||
|
| `FAILED_FINAL` | Terminal fehlgeschlagen, wird nicht erneut verarbeitet |
|
||||||
| `SKIPPED_ALREADY_PROCESSED` | Übersprungen – Dokument bereits erfolgreich verarbeitet |
|
| `SKIPPED_ALREADY_PROCESSED` | Übersprungen – Dokument bereits erfolgreich verarbeitet |
|
||||||
| `SKIPPED_FINAL_FAILURE` | Übersprungen – Dokument terminal fehlgeschlagen |
|
| `SKIPPED_FINAL_FAILURE` | Übersprungen – Dokument terminal fehlgeschlagen |
|
||||||
|
|
||||||
|
`PROCESSING` ist ein transienter Zwischenstatus während eines laufenden Verarbeitungsversuchs
|
||||||
|
und wird nicht als dauerhafter Endstatus gespeichert.
|
||||||
|
|
||||||
### Retry-Regeln
|
### Retry-Regeln
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import de.gecheckt.pdf.umbenenner.application.port.out.RunLockUnavailableExcepti
|
|||||||
/**
|
/**
|
||||||
* File-based implementation of {@link RunLockPort} that uses a lock file to prevent concurrent runs.
|
* File-based implementation of {@link RunLockPort} that uses a lock file to prevent concurrent runs.
|
||||||
* <p>
|
* <p>
|
||||||
* AP-006 Implementation: Creates an exclusive lock file on acquire and deletes it on release.
|
* Creates an exclusive lock file on acquire and deletes it on release.
|
||||||
* If the lock file already exists, {@link #acquire()} throws {@link RunLockUnavailableException}
|
* If the lock file already exists, {@link #acquire()} throws {@link RunLockUnavailableException}
|
||||||
* to signal that another instance is already running.
|
* to signal that another instance is already running.
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ public class PdfTextExtractionPortAdapter implements PdfTextExtractionPort {
|
|||||||
try {
|
try {
|
||||||
int pageCount = document.getNumberOfPages();
|
int pageCount = document.getNumberOfPages();
|
||||||
|
|
||||||
// AP-003: Handle case of zero pages as technical error
|
// Handle case of zero pages as technical error
|
||||||
// (PdfPageCount requires >= 1, so this is a constraint violation)
|
// (PdfPageCount requires >= 1, so this is a constraint violation)
|
||||||
if (pageCount < 1) {
|
if (pageCount < 1) {
|
||||||
return new PdfExtractionTechnicalError(
|
return new PdfExtractionTechnicalError(
|
||||||
@@ -124,7 +124,7 @@ public class PdfTextExtractionPortAdapter implements PdfTextExtractionPort {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// All I/O and PDFBox loading/parsing errors are technical errors in AP-003
|
// All I/O and PDFBox loading/parsing errors are technical errors
|
||||||
String errorMessage = e.getMessage() != null ? e.getMessage() : e.toString();
|
String errorMessage = e.getMessage() != null ? e.getMessage() : e.toString();
|
||||||
return new PdfExtractionTechnicalError(
|
return new PdfExtractionTechnicalError(
|
||||||
"Failed to load or parse PDF: " + errorMessage,
|
"Failed to load or parse PDF: " + errorMessage,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
|
|||||||
/**
|
/**
|
||||||
* File-system based implementation of {@link SourceDocumentCandidatesPort}.
|
* File-system based implementation of {@link SourceDocumentCandidatesPort}.
|
||||||
* <p>
|
* <p>
|
||||||
* AP-002 Implementation: Scans a configured source folder and returns only PDF files
|
* Scans a configured source folder and returns only PDF files
|
||||||
* (by extension) as {@link SourceDocumentCandidate} objects.
|
* (by extension) as {@link SourceDocumentCandidate} objects.
|
||||||
* <p>
|
* <p>
|
||||||
* Design:
|
* Design:
|
||||||
@@ -29,13 +29,11 @@ import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
|
|||||||
* <p>
|
* <p>
|
||||||
* Non-goals:
|
* Non-goals:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>No PDF validation (that is AP-003)</li>
|
* <li>No PDF structure validation</li>
|
||||||
* <li>No recursion into subdirectories</li>
|
* <li>No recursion into subdirectories</li>
|
||||||
* <li>No content evaluation (that happens in AP-004: brauchbarer Text assessment)</li>
|
* <li>No content evaluation (text usability is assessed during document processing)</li>
|
||||||
* <li>No fachlich evaluation of candidates</li>
|
* <li>No fachlich evaluation of candidates</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
|
||||||
* @since M3-AP-002
|
|
||||||
*/
|
*/
|
||||||
public class SourceDocumentCandidatesPortAdapter implements SourceDocumentCandidatesPort {
|
public class SourceDocumentCandidatesPortAdapter implements SourceDocumentCandidatesPort {
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ import de.gecheckt.pdf.umbenenner.application.port.out.PersistenceSchemaInitiali
|
|||||||
* <li>Target-copy column ({@code final_target_file_name}) to {@code processing_attempt}</li>
|
* <li>Target-copy column ({@code final_target_file_name}) to {@code processing_attempt}</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <h2>M4→current-schema status migration</h2>
|
* <h2>Legacy-state migration</h2>
|
||||||
* <p>
|
* <p>
|
||||||
* Documents in an earlier positive intermediate state ({@code SUCCESS} recorded without
|
* Documents in an earlier positive intermediate state ({@code SUCCESS} recorded without
|
||||||
* a validated naming proposal) are idempotently migrated to {@code READY_FOR_AI} so that
|
* a validated naming proposal) are idempotently migrated to {@code READY_FOR_AI} so that
|
||||||
@@ -178,7 +178,7 @@ public class SqliteSchemaInitializationAdapter implements PersistenceSchemaIniti
|
|||||||
};
|
};
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// M4→current-schema status migration
|
// Legacy-state status migration
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -836,7 +836,7 @@ class StartConfigurationValidatorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* M3/AP-007: Focused tests for source folder validation using mocked filesystem checks.
|
* Focused tests for source folder validation using mocked filesystem checks.
|
||||||
* <p>
|
* <p>
|
||||||
* These tests verify the four critical paths for source folder validation without
|
* These tests verify the four critical paths for source folder validation without
|
||||||
* relying on platform-dependent filesystem permissions or the actual FS state.
|
* relying on platform-dependent filesystem permissions or the actual FS state.
|
||||||
@@ -941,7 +941,7 @@ class StartConfigurationValidatorTest {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Mock: simulate path exists, is directory, but is not readable
|
// Mock: simulate path exists, is directory, but is not readable
|
||||||
// This is the critical M3/AP-007 case that is hard to test on actual FS
|
// This is the critical case that is hard to test on actual FS
|
||||||
StartConfigurationValidator.SourceFolderChecker mockChecker = path ->
|
StartConfigurationValidator.SourceFolderChecker mockChecker = path ->
|
||||||
"- source.folder: directory is not readable: " + path;
|
"- source.folder: directory is not readable: " + path;
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for {@link Sha256FingerprintAdapter}.
|
* Unit tests for {@link Sha256FingerprintAdapter}.
|
||||||
*
|
|
||||||
* @since M4-AP-002
|
|
||||||
*/
|
*/
|
||||||
class Sha256FingerprintAdapterTest {
|
class Sha256FingerprintAdapterTest {
|
||||||
|
|
||||||
|
|||||||
@@ -28,12 +28,10 @@ import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
|
|||||||
/**
|
/**
|
||||||
* Tests for {@link PdfTextExtractionPortAdapter}.
|
* Tests for {@link PdfTextExtractionPortAdapter}.
|
||||||
* <p>
|
* <p>
|
||||||
* M3-AP-003: Minimal tests validating basic extraction functionality and technical error handling.
|
* Validates basic extraction functionality and technical error handling.
|
||||||
* In AP-003 scope: all extraction problems are treated as TechnicalError, not ContentError.
|
* All extraction problems are treated as {@link de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionTechnicalError},
|
||||||
* No fachliche validation of text content (that is AP-004).
|
* not content errors. Content usability (text quality assessment) is handled in the application layer.
|
||||||
* PDFs are created programmatically using PDFBox to avoid external dependencies on test files.
|
* PDFs are created programmatically using PDFBox to avoid external dependencies on test files.
|
||||||
*
|
|
||||||
* @since M3-AP-003
|
|
||||||
*/
|
*/
|
||||||
class PdfTextExtractionPortAdapterTest {
|
class PdfTextExtractionPortAdapterTest {
|
||||||
|
|
||||||
@@ -170,8 +168,8 @@ class PdfTextExtractionPortAdapterTest {
|
|||||||
|
|
||||||
PdfExtractionResult result = adapter.extractTextAndPageCount(candidate);
|
PdfExtractionResult result = adapter.extractTextAndPageCount(candidate);
|
||||||
|
|
||||||
// AP-003: Empty text is SUCCESS, not an error
|
// Empty text is SUCCESS at extraction level, not an error
|
||||||
// Fachliche Bewertung of text content happens in AP-004
|
// Fachliche Bewertung of text content happens in the application layer
|
||||||
assertInstanceOf(PdfExtractionSuccess.class, result);
|
assertInstanceOf(PdfExtractionSuccess.class, result);
|
||||||
PdfExtractionSuccess success = (PdfExtractionSuccess) result;
|
PdfExtractionSuccess success = (PdfExtractionSuccess) result;
|
||||||
assertEquals(1, success.pageCount().value());
|
assertEquals(1, success.pageCount().value());
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link SourceDocumentCandidatesPortAdapter}.
|
* Tests for {@link SourceDocumentCandidatesPortAdapter}.
|
||||||
*
|
|
||||||
* @since M3-AP-002
|
|
||||||
*/
|
*/
|
||||||
class SourceDocumentCandidatesPortAdapterTest {
|
class SourceDocumentCandidatesPortAdapterTest {
|
||||||
|
|
||||||
@@ -198,7 +196,7 @@ class SourceDocumentCandidatesPortAdapterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testLoadCandidates_EmptyPdfFilesAreIncluded() throws IOException {
|
void testLoadCandidates_EmptyPdfFilesAreIncluded() throws IOException {
|
||||||
// Create empty PDF files (M3-AP-002 requirement: PDF-Dateien im Quellordner)
|
// Create empty PDF files
|
||||||
Files.createFile(tempDir.resolve("empty1.pdf"));
|
Files.createFile(tempDir.resolve("empty1.pdf"));
|
||||||
Files.createFile(tempDir.resolve("empty2.pdf"));
|
Files.createFile(tempDir.resolve("empty2.pdf"));
|
||||||
// Also add a non-empty PDF for contrast
|
// Also add a non-empty PDF for contrast
|
||||||
@@ -207,7 +205,7 @@ class SourceDocumentCandidatesPortAdapterTest {
|
|||||||
List<SourceDocumentCandidate> candidates = adapter.loadCandidates();
|
List<SourceDocumentCandidate> candidates = adapter.loadCandidates();
|
||||||
|
|
||||||
assertEquals(3, candidates.size(),
|
assertEquals(3, candidates.size(),
|
||||||
"Empty PDF files should be included as candidates; content evaluation happens in AP-004");
|
"Empty PDF files should be included as candidates; content evaluation happens during document processing");
|
||||||
assertTrue(candidates.stream().allMatch(c -> c.uniqueIdentifier().endsWith(".pdf")),
|
assertTrue(candidates.stream().allMatch(c -> c.uniqueIdentifier().endsWith(".pdf")),
|
||||||
"All candidates should be PDF files");
|
"All candidates should be PDF files");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import static org.junit.jupiter.api.Assertions.*;
|
|||||||
* Tests verify transactional semantics: successful commits, rollback on first-write failure,
|
* Tests verify transactional semantics: successful commits, rollback on first-write failure,
|
||||||
* rollback on second-write failure, and proper handling of DocumentPersistenceException.
|
* rollback on second-write failure, and proper handling of DocumentPersistenceException.
|
||||||
*
|
*
|
||||||
* @since M4-AP-006
|
|
||||||
*/
|
*/
|
||||||
class SqliteUnitOfWorkAdapterTest {
|
class SqliteUnitOfWorkAdapterTest {
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class DefaultRetryDecisionEvaluatorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void evaluate_subsequentContentErrors_alwaysReturnContentErrorFinal() {
|
void evaluate_subsequentContentErrors_alwaysReturnContentErrorFinal() {
|
||||||
// Any count >= 1 results in final (covers legacy M4-M6 data with higher counts)
|
// Any count >= 1 results in final (covers legacy data with higher counts)
|
||||||
for (int count = 1; count <= 5; count++) {
|
for (int count = 1; count <= 5; count++) {
|
||||||
FailureCounters counters = new FailureCounters(count, 0);
|
FailureCounters counters = new FailureCounters(count, 0);
|
||||||
|
|
||||||
@@ -185,7 +185,7 @@ class DefaultRetryDecisionEvaluatorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void evaluate_transientError_legacyDataWithHigherCounts_finalizesCorrectly() {
|
void evaluate_transientError_legacyDataWithHigherCounts_finalizesCorrectly() {
|
||||||
// Existing M4-M6 data may have counter values beyond normal expectations;
|
// Existing legacy data may have counter values beyond normal expectations;
|
||||||
// the evaluator must still apply the threshold check consistently
|
// the evaluator must still apply the threshold check consistently
|
||||||
FailureCounters counters = new FailureCounters(3, 5);
|
FailureCounters counters = new FailureCounters(3, 5);
|
||||||
|
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ class DocumentProcessingCoordinatorTest {
|
|||||||
assertEquals(ProcessingStatus.PROPOSAL_READY, record.overallStatus());
|
assertEquals(ProcessingStatus.PROPOSAL_READY, record.overallStatus());
|
||||||
assertEquals(0, record.failureCounters().contentErrorCount());
|
assertEquals(0, record.failureCounters().contentErrorCount());
|
||||||
assertEquals(0, record.failureCounters().transientErrorCount());
|
assertEquals(0, record.failureCounters().transientErrorCount());
|
||||||
// lastSuccessInstant is null in M5; it is set by the target-copy stage (M6)
|
// lastSuccessInstant is null after AI naming proposal; it is set only after the target-copy stage
|
||||||
assertNull(record.lastSuccessInstant());
|
assertNull(record.lastSuccessInstant());
|
||||||
assertNull(record.lastFailureInstant());
|
assertNull(record.lastFailureInstant());
|
||||||
}
|
}
|
||||||
@@ -283,7 +283,7 @@ class DocumentProcessingCoordinatorTest {
|
|||||||
// Counters unchanged on naming proposal success
|
// Counters unchanged on naming proposal success
|
||||||
assertEquals(0, record.failureCounters().contentErrorCount());
|
assertEquals(0, record.failureCounters().contentErrorCount());
|
||||||
assertEquals(1, record.failureCounters().transientErrorCount());
|
assertEquals(1, record.failureCounters().transientErrorCount());
|
||||||
// lastSuccessInstant is null in M5; it is set by the target-copy stage (M6)
|
// lastSuccessInstant is null after AI naming proposal; it is set only after the target-copy stage
|
||||||
assertNull(record.lastSuccessInstant());
|
assertNull(record.lastSuccessInstant());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -599,7 +599,7 @@ class DocumentProcessingCoordinatorTest {
|
|||||||
@Test
|
@Test
|
||||||
void process_knownDocument_namingProposalReady_lastSuccessInstantNullAndLastFailureInstantFromPreviousRecord() {
|
void process_knownDocument_namingProposalReady_lastSuccessInstantNullAndLastFailureInstantFromPreviousRecord() {
|
||||||
// Prüft, dass bei PROPOSAL_READY am known-Dokument lastSuccessInstant null bleibt
|
// Prüft, dass bei PROPOSAL_READY am known-Dokument lastSuccessInstant null bleibt
|
||||||
// (M6 setzt ihn erst nach der Zielkopie) und lastFailureInstant aus dem Vorgänger übernommen wird
|
// (wird erst nach der Zielkopie gesetzt) und lastFailureInstant aus dem Vorgänger übernommen wird
|
||||||
Instant previousFailureInstant = Instant.parse("2025-01-15T10:00:00Z");
|
Instant previousFailureInstant = Instant.parse("2025-01-15T10:00:00Z");
|
||||||
DocumentRecord existingRecord = new DocumentRecord(
|
DocumentRecord existingRecord = new DocumentRecord(
|
||||||
fingerprint,
|
fingerprint,
|
||||||
@@ -621,7 +621,7 @@ class DocumentProcessingCoordinatorTest {
|
|||||||
|
|
||||||
DocumentRecord updated = recordRepo.updatedRecords.get(0);
|
DocumentRecord updated = recordRepo.updatedRecords.get(0);
|
||||||
assertNull(updated.lastSuccessInstant(),
|
assertNull(updated.lastSuccessInstant(),
|
||||||
"lastSuccessInstant muss nach PROPOSAL_READY null bleiben (wird erst von M6 gesetzt)");
|
"lastSuccessInstant muss nach PROPOSAL_READY null bleiben (wird erst nach der Zielkopie gesetzt)");
|
||||||
assertEquals(previousFailureInstant, updated.lastFailureInstant(),
|
assertEquals(previousFailureInstant, updated.lastFailureInstant(),
|
||||||
"lastFailureInstant muss bei PROPOSAL_READY den Vorgänger-Wert beibehalten");
|
"lastFailureInstant muss bei PROPOSAL_READY den Vorgänger-Wert beibehalten");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -262,7 +262,7 @@ class ProcessingOutcomeTransitionTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void forKnownDocument_transientError_legacyHighCounters_stillFinalise() {
|
void forKnownDocument_transientError_legacyHighCounters_stillFinalise() {
|
||||||
// Legacy data from M4-M6 may have counters well above normal expectations.
|
// Legacy data may have counters well above normal expectations.
|
||||||
// The threshold check must still apply correctly.
|
// The threshold check must still apply correctly.
|
||||||
TechnicalDocumentError outcome = new TechnicalDocumentError(candidate(), "error", null);
|
TechnicalDocumentError outcome = new TechnicalDocumentError(candidate(), "error", null);
|
||||||
FailureCounters existing = new FailureCounters(3, 10); // already far above limit
|
FailureCounters existing = new FailureCounters(3, 10); // already far above limit
|
||||||
|
|||||||
@@ -728,7 +728,7 @@ class BatchRunProcessingUseCaseTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Batch-level M7 integration tests (real coordinator + capturing repos)
|
// Batch-level integration tests (real coordinator + capturing repos)
|
||||||
// These prove that skip and finalization semantics work in the actual batch run,
|
// These prove that skip and finalization semantics work in the actual batch run,
|
||||||
// not just at the coordinator unit-test level.
|
// not just at the coordinator unit-test level.
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import java.util.List;
|
|||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AP-008: Executable JAR smoke tests for M1 target state verification.
|
* Executable JAR smoke tests verifying that the shaded JAR starts correctly.
|
||||||
* <p>
|
* <p>
|
||||||
* These tests verify that the shaded executable JAR can be run via {@code java -jar}
|
* These tests verify that the shaded executable JAR can be run via {@code java -jar}
|
||||||
* and behaves correctly for both success and invalid configuration scenarios.
|
* and behaves correctly for both success and invalid configuration scenarios.
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ import java.util.Objects;
|
|||||||
* <p>
|
* <p>
|
||||||
* This context is independent of individual document processing and contains
|
* This context is independent of individual document processing and contains
|
||||||
* no business logic. It is purely a technical container for run identity and timing.
|
* no business logic. It is purely a technical container for run identity and timing.
|
||||||
*
|
|
||||||
* @since M2-AP-003
|
|
||||||
*/
|
*/
|
||||||
public final class BatchRunContext {
|
public final class BatchRunContext {
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import java.util.Objects;
|
|||||||
* key for all subsequent persistence lookups and history entries. It is independent of
|
* key for all subsequent persistence lookups and history entries. It is independent of
|
||||||
* the file name, path, or any metadata — only the raw file content determines the value.
|
* the file name, path, or any metadata — only the raw file content determines the value.
|
||||||
* <p>
|
* <p>
|
||||||
* <strong>Identification semantics (M4):</strong>
|
* <strong>Identification semantics:</strong>
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Two files with identical content have the same fingerprint and are treated as
|
* <li>Two files with identical content have the same fingerprint and are treated as
|
||||||
* the same document, regardless of their location or name.</li>
|
* the same document, regardless of their location or name.</li>
|
||||||
@@ -29,7 +29,6 @@ import java.util.Objects;
|
|||||||
*
|
*
|
||||||
* @param sha256Hex lowercase hex encoding of the SHA-256 digest (exactly 64 characters,
|
* @param sha256Hex lowercase hex encoding of the SHA-256 digest (exactly 64 characters,
|
||||||
* characters {@code [0-9a-f]})
|
* characters {@code [0-9a-f]})
|
||||||
* @since M4-AP-001
|
|
||||||
*/
|
*/
|
||||||
public record DocumentFingerprint(String sha256Hex) {
|
public record DocumentFingerprint(String sha256Hex) {
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import java.util.Objects;
|
|||||||
* (unless the source file is modified and re-scanned in a later run).
|
* (unless the source file is modified and re-scanned in a later run).
|
||||||
*
|
*
|
||||||
* @param reason a human-readable explanation of why extraction failed (non-null, non-empty)
|
* @param reason a human-readable explanation of why extraction failed (non-null, non-empty)
|
||||||
* @since M3-AP-001
|
|
||||||
*/
|
*/
|
||||||
public record PdfExtractionContentError(
|
public record PdfExtractionContentError(
|
||||||
String reason
|
String reason
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ package de.gecheckt.pdf.umbenenner.domain.model;
|
|||||||
* <li>Distinct error types: allows retry logic to differentiate recoverable from non-recoverable</li>
|
* <li>Distinct error types: allows retry logic to differentiate recoverable from non-recoverable</li>
|
||||||
* <li>No PDFBox or filesystem types: pure domain representation</li>
|
* <li>No PDFBox or filesystem types: pure domain representation</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
|
||||||
* @since M3-AP-001
|
|
||||||
*/
|
*/
|
||||||
public sealed interface PdfExtractionResult
|
public sealed interface PdfExtractionResult
|
||||||
permits PdfExtractionSuccess, PdfExtractionContentError, PdfExtractionTechnicalError {
|
permits PdfExtractionSuccess, PdfExtractionContentError, PdfExtractionTechnicalError {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import java.util.Objects;
|
|||||||
*
|
*
|
||||||
* @param extractedText the full text content extracted from the PDF (non-null, may be empty string)
|
* @param extractedText the full text content extracted from the PDF (non-null, may be empty string)
|
||||||
* @param pageCount the number of pages in the PDF (non-null, validated >= 1)
|
* @param pageCount the number of pages in the PDF (non-null, validated >= 1)
|
||||||
* @since M3-AP-001
|
|
||||||
*/
|
*/
|
||||||
public record PdfExtractionSuccess(
|
public record PdfExtractionSuccess(
|
||||||
String extractedText,
|
String extractedText,
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import java.util.Objects;
|
|||||||
*
|
*
|
||||||
* @param errorMessage a description of what went wrong (non-null, non-empty)
|
* @param errorMessage a description of what went wrong (non-null, non-empty)
|
||||||
* @param cause the underlying exception, if any (may be null)
|
* @param cause the underlying exception, if any (may be null)
|
||||||
* @since M3-AP-001
|
|
||||||
*/
|
*/
|
||||||
public record PdfExtractionTechnicalError(
|
public record PdfExtractionTechnicalError(
|
||||||
String errorMessage,
|
String errorMessage,
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ package de.gecheckt.pdf.umbenenner.domain.model;
|
|||||||
* <li>Self-documenting: {@code PdfPageCount} is clearer than naked {@code int}</li>
|
* <li>Self-documenting: {@code PdfPageCount} is clearer than naked {@code int}</li>
|
||||||
* <li>Future-extensible: can add validation rules per milestone without signature changes</li>
|
* <li>Future-extensible: can add validation rules per milestone without signature changes</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
|
||||||
* @since M3-AP-001
|
|
||||||
*/
|
*/
|
||||||
public record PdfPageCount(int value) {
|
public record PdfPageCount(int value) {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ import java.util.Objects;
|
|||||||
* RunId is intentionally simple: a non-null, immutable string value.
|
* RunId is intentionally simple: a non-null, immutable string value.
|
||||||
* Implementations may choose UUID format, timestamp-based IDs, or sequential IDs.
|
* Implementations may choose UUID format, timestamp-based IDs, or sequential IDs.
|
||||||
* The internal structure is opaque to consumers.
|
* The internal structure is opaque to consumers.
|
||||||
*
|
|
||||||
* @since M2-AP-003
|
|
||||||
*/
|
*/
|
||||||
public final class RunId implements Serializable {
|
public final class RunId implements Serializable {
|
||||||
|
|
||||||
|
|||||||
@@ -16,14 +16,13 @@ import java.util.Objects;
|
|||||||
* Fields:
|
* Fields:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@code uniqueIdentifier} — human-readable name for logging and correlation (e.g. filename)</li>
|
* <li>{@code uniqueIdentifier} — human-readable name for logging and correlation (e.g. filename)</li>
|
||||||
* <li>{@code fileSizeBytes} — file size for metadata and tracing; may be zero for empty files (content evaluation happens later in AP-004)</li>
|
* <li>{@code fileSizeBytes} — file size for metadata and tracing; may be zero for empty files (content evaluation happens during processing)</li>
|
||||||
* <li>{@code locator} — opaque reference passed through unchanged to the extraction adapter;
|
* <li>{@code locator} — opaque reference passed through unchanged to the extraction adapter;
|
||||||
* Domain and Application never interpret its value</li>
|
* Domain and Application never interpret its value</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* <p>
|
* <p>
|
||||||
* No java.io.File or java.nio.file.Path references appear in this record.
|
* No java.io.File or java.nio.file.Path references appear in this record.
|
||||||
*
|
*
|
||||||
* @since M3-AP-001
|
|
||||||
*/
|
*/
|
||||||
public record SourceDocumentCandidate(
|
public record SourceDocumentCandidate(
|
||||||
String uniqueIdentifier,
|
String uniqueIdentifier,
|
||||||
@@ -36,12 +35,12 @@ public record SourceDocumentCandidate(
|
|||||||
* Ensures all parameters are non-null and meaningful:
|
* Ensures all parameters are non-null and meaningful:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@code uniqueIdentifier} must be non-null and non-empty</li>
|
* <li>{@code uniqueIdentifier} must be non-null and non-empty</li>
|
||||||
* <li>{@code fileSizeBytes} must be non-negative (may be zero for empty files; content evaluation is AP-004)</li>
|
* <li>{@code fileSizeBytes} must be non-negative (may be zero for empty files; content evaluation happens during processing)</li>
|
||||||
* <li>{@code locator} must be non-null</li>
|
* <li>{@code locator} must be non-null</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @param uniqueIdentifier non-null, non-empty identifier for logging and correlation
|
* @param uniqueIdentifier non-null, non-empty identifier for logging and correlation
|
||||||
* @param fileSizeBytes must be >= 0 (may be 0; content evaluation happens in AP-004)
|
* @param fileSizeBytes must be >= 0 (may be 0; content evaluation happens during processing)
|
||||||
* @param locator non-null opaque locator; only adapters interpret its value
|
* @param locator non-null opaque locator; only adapters interpret its value
|
||||||
* @throws NullPointerException if uniqueIdentifier or locator is null
|
* @throws NullPointerException if uniqueIdentifier or locator is null
|
||||||
* @throws IllegalArgumentException if uniqueIdentifier is empty or fileSizeBytes < 0
|
* @throws IllegalArgumentException if uniqueIdentifier is empty or fileSizeBytes < 0
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ import java.util.Objects;
|
|||||||
* Coupling: Both the scan adapter and the extraction adapter live in the same
|
* Coupling: Both the scan adapter and the extraction adapter live in the same
|
||||||
* {@code adapter-out} module and share the same encoding convention for the value.
|
* {@code adapter-out} module and share the same encoding convention for the value.
|
||||||
* This is an intentional intra-adapter contract, not a cross-layer concern.
|
* This is an intentional intra-adapter contract, not a cross-layer concern.
|
||||||
*
|
|
||||||
* @since M3-AP-001
|
|
||||||
*/
|
*/
|
||||||
public record SourceDocumentLocator(String value) {
|
public record SourceDocumentLocator(String value) {
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user