M13 vollständig abgeschlossen: V2.0-Freigabe (AP-001 bis AP-009)
- AP-001: Betriebs- und Startdokumentation für GUI und headless konsolidiert (betrieb.md, README.md) - AP-002: Endbenutzer-Bedienanleitung gui-bedienanleitung.md angelegt (deskriptiv, 13 Kapitel, deutsch, Windows-Hinweise) - AP-003: Konfigurationsbeispiele docs/examples/application.properties und docs/examples/prompt.txt konsolidiert, konsistent mit Standardvorlage - AP-004: Regressionstests für headless Abwärtskompatibilität (JAR-Smoke-IT mit --config-Varianten und JavaFX-Freiheit) - AP-005: GUI-Smoke-Tests für V2.0-Kernumfang vervollständigt (Startup-Notice-Sichtbarkeit im Header) - AP-006: Build- und Packaging-Dokumentation im Abschnitt "Build und Packaging" in betrieb.md, README-Artefaktnamen korrigiert - AP-007: Integrierte Gesamtprüfung durchgeführt, V2.0-Abschnitt in befundliste.md — keine Release-Blocker, zwei nicht blockierende Restpunkte (R1 ByteBuddy-Warning, R2 fehlender visueller GUI-Render-Test) - AP-008: entfiel (keine Release-Blocker zu beheben) - AP-009: Finale Gesamtprüfung, Freigabedokument docs/freigabe-v2_0.md mit Git-HEAD, Build-/Test-Ergebnissen, Freigabeaussage. Ein während der Stichprobe entdeckter Doku-Defekt (R3: API-Key-Legacy-Variable) wurde unmittelbar in gui-bedienanleitung.md korrigiert. V2.0 ist freigabefähig. 1.403 Tests grün, 0 Failures, 0 Errors. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,14 +4,21 @@ Ein lokal gestartetes Java-Programm zur KI-gestützten Umbenennung bereits OCR-v
|
||||
|
||||
Die Anwendung liest PDF-Dateien aus einem konfigurierbaren Quellordner, extrahiert den Text, ermittelt daraus per KI einen normierten Dateinamen und legt **eine Kopie** im Zielordner ab. Die Quelldateien bleiben unverändert.
|
||||
|
||||
> **V2.0:** Die Anwendung enthält ab V2.0 eine lokale JavaFX-Desktop-GUI als Standardstart.
|
||||
> Die GUI dient der Konfiguration, Validierung und technischen Diagnose.
|
||||
> Der headless Batch-Betrieb bleibt über `--headless` vollständig erhalten.
|
||||
> Details zum Betrieb: [`docs/betrieb.md`](docs/betrieb.md)
|
||||
|
||||
## Zielbild
|
||||
|
||||
Der PDF-Umbenenner ist bewusst als schlanke Batch-Anwendung ausgelegt:
|
||||
Der PDF-Umbenenner ist als schlanke, lokal gestartete Anwendung ausgelegt:
|
||||
|
||||
- **Java 21**
|
||||
- **Maven Multi-Module**
|
||||
- **ausführbares Standalone-JAR**
|
||||
- **lokaler Start**, z. B. über den **Windows Task Scheduler**
|
||||
- **ausführbares Standalone-JAR** (ein gemeinsames JAR für GUI und headless)
|
||||
- **GUI-Standardstart** ab V2.0 (JavaFX-Desktop, offiziell Windows)
|
||||
- **headless Betrieb** über `--headless`, z. B. für den **Windows Task Scheduler**
|
||||
- **`--config <pfad>`** für GUI und headless
|
||||
- **kein Webserver**
|
||||
- **kein Applikationsserver**
|
||||
- **keine Dauerlauf-Anwendung**
|
||||
@@ -86,6 +93,7 @@ Das Projekt ist strikt nach **Ports and Adapters / Hexagonal Architecture** aufg
|
||||
- `pdf-umbenenner-domain`
|
||||
- `pdf-umbenenner-application`
|
||||
- `pdf-umbenenner-adapter-in-cli`
|
||||
- `pdf-umbenenner-adapter-in-gui`
|
||||
- `pdf-umbenenner-adapter-out`
|
||||
- `pdf-umbenenner-bootstrap`
|
||||
|
||||
@@ -135,13 +143,30 @@ Unter Windows:
|
||||
|
||||
## Start
|
||||
|
||||
Das ausführbare Artefakt wird im Bootstrap-Modul erzeugt. Der Start erfolgt als normales Java-Programm:
|
||||
Das ausführbare Artefakt wird im Bootstrap-Modul erzeugt.
|
||||
|
||||
**GUI-Standardstart** (öffnet die JavaFX-Desktop-Oberfläche):
|
||||
|
||||
```bash
|
||||
java -jar <bootstrap-jar>.jar
|
||||
java -jar pdf-umbenenner-bootstrap/target/pdf-umbenenner-bootstrap-0.0.1-SNAPSHOT.jar
|
||||
```
|
||||
|
||||
Die konkrete JAR-Datei hängt vom aktuellen Build-Stand ab.
|
||||
**headless Betrieb** (Batch-/Scheduler-Start ohne GUI):
|
||||
|
||||
```bash
|
||||
java -jar pdf-umbenenner-bootstrap/target/pdf-umbenenner-bootstrap-0.0.1-SNAPSHOT.jar --headless
|
||||
```
|
||||
|
||||
**Explizite Konfigurationsdatei** (für GUI und headless):
|
||||
|
||||
```bash
|
||||
java -jar pdf-umbenenner-bootstrap/target/pdf-umbenenner-bootstrap-0.0.1-SNAPSHOT.jar --config C:\Pfad\zur\config.properties
|
||||
java -jar pdf-umbenenner-bootstrap/target/pdf-umbenenner-bootstrap-0.0.1-SNAPSHOT.jar --headless --config C:\Pfad\zur\config.properties
|
||||
```
|
||||
|
||||
Das JAR ist das einzige Distributionsartefakt und enthält JavaFX für den GUI-Start bereits
|
||||
integriert. Ausführliche Build-, Packaging- und Starthinweise sowie Informationen zur JavaFX-
|
||||
Integration und zum headless Betrieb befinden sich in [`docs/betrieb.md`](docs/betrieb.md).
|
||||
|
||||
## Logging, Status und Nachvollziehbarkeit
|
||||
|
||||
@@ -160,7 +185,10 @@ Die maßgeblichen Dokumente sind:
|
||||
- `CLAUDE.md`
|
||||
- `docs/specs/technik-und-architektur.md`
|
||||
- `docs/specs/fachliche-anforderungen.md`
|
||||
- `docs/specs/meilensteine.md`
|
||||
- `docs/specs/meilensteine-v2_0.md`
|
||||
- `docs/betrieb.md`
|
||||
- `docs/gui-bedienanleitung.md`
|
||||
- `docs/freigabe-v2_0.md`
|
||||
- `docs/workpackages/...`
|
||||
|
||||
Empfohlene Leserichtung:
|
||||
@@ -169,7 +197,9 @@ Empfohlene Leserichtung:
|
||||
2. technische Zielarchitektur
|
||||
3. fachliche Anforderungen
|
||||
4. Meilensteine
|
||||
5. aktives Arbeitspaket
|
||||
5. `docs/betrieb.md` für Betriebs- und Startdetails
|
||||
6. `docs/gui-bedienanleitung.md` für die GUI-Bedienung
|
||||
7. aktives Arbeitspaket
|
||||
|
||||
## Entwicklungsleitplanken
|
||||
|
||||
@@ -181,14 +211,16 @@ Empfohlene Leserichtung:
|
||||
|
||||
## Status des Projekts
|
||||
|
||||
Das Repository verfolgt einen inkrementellen, meilensteinbasierten Ausbau. Der aktuelle Produktstand baut auf einem vollständig implementierten Kern für:
|
||||
Das Repository verfolgt einen inkrementellen, meilensteinbasierten Ausbau. Der aktuelle Produktstand (V2.0) baut auf einem vollständig implementierten Kern für:
|
||||
|
||||
- Konfiguration und Startvalidierung
|
||||
- Quellordner-Scan und PDF-Textauslese
|
||||
- Fingerprint, SQLite-Persistenz und Idempotenz
|
||||
- KI-Integration für Benennungsvorschläge
|
||||
- KI-Integration für Benennungsvorschläge (OpenAI-kompatibel und Anthropic Claude)
|
||||
- Dateinamensbildung und Zielkopie
|
||||
- Retry-Logik, Logging und betriebliche Robustheit
|
||||
- JavaFX-Desktop-GUI als Standardstart (Konfigurationseditor, Validierung, technische Tests)
|
||||
- headless Batch-Betrieb über `--headless` (rückwärtskompatibel zu V1.x)
|
||||
|
||||
## Lizenz / Nutzung
|
||||
|
||||
|
||||
@@ -173,3 +173,145 @@ Spezifikationen (technik-und-architektur.md, fachliche-anforderungen.md, CLAUDE.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
# V2.0-Gesamtstand – Integrierte Prüfung (Stand 2026-04-20)
|
||||
|
||||
**Prüfgrundlage:** Vollständiger Maven-Reactor-Build mit allen Tests (clean verify, `-DskipPitest=true`),
|
||||
Code-Review gegen die verbindliche Spec-Trias (technik-und-architektur.md, fachliche-anforderungen.md,
|
||||
CLAUDE.md), Sichtprüfung Dokumentation und Konfigurationsbeispiele.
|
||||
|
||||
---
|
||||
|
||||
## Build- und Testergebnisse
|
||||
|
||||
**Ausgeführtes Kommando:**
|
||||
```
|
||||
.\mvnw.cmd clean verify -pl pdf-umbenenner-domain,pdf-umbenenner-application,pdf-umbenenner-adapter-out,pdf-umbenenner-adapter-in-cli,pdf-umbenenner-adapter-in-gui,pdf-umbenenner-bootstrap --also-make -DskipPitest=true
|
||||
```
|
||||
|
||||
**Gesamtergebnis: BUILD SUCCESS**
|
||||
**Gesamtlaufzeit:** 01:18 min
|
||||
**Datum:** 2026-04-20
|
||||
|
||||
| Modul | Tests | Failures | Errors | Skipped |
|
||||
|---|---|---|---|---|
|
||||
| `pdf-umbenenner-domain` | 227 | 0 | 0 | 0 |
|
||||
| `pdf-umbenenner-application` | 455 | 0 | 0 | 0 |
|
||||
| `pdf-umbenenner-adapter-in-cli` | 8 | 0 | 0 | 0 |
|
||||
| `pdf-umbenenner-adapter-in-gui` | 190 | 0 | 0 | 0 |
|
||||
| `pdf-umbenenner-adapter-out` | 371 | 0 | 0 | 0 |
|
||||
| `pdf-umbenenner-bootstrap` | 147 | 0 | 0 | 0 |
|
||||
| **Gesamt** | **1.398** | **0** | **0** | **0** |
|
||||
|
||||
Alle Module bauen erfolgreich. Alle Tests bestehen. Das ausführbare Shade-JAR wird erzeugt und
|
||||
enthält JavaFX (Win-Classifier), alle Module, PDFBox, SQLite-JDBC und Log4j2.
|
||||
|
||||
**Warnungen im Build:**
|
||||
- `WARNING: A Java agent has been loaded dynamically (byte-buddy-agent)` – dies ist ein bekannter
|
||||
Mockito/ByteBuddy-Hinweis, der in Tests auftritt. Kein funktionaler Defekt. Tritt seit V1.1 auf
|
||||
und gilt als akzeptiert.
|
||||
|
||||
---
|
||||
|
||||
## Prüfpunkte gegen die V2.0-Spezifikation (1–20)
|
||||
|
||||
| Nr. | Prüfpunkt | Status | Begründung |
|
||||
|---|---|---|---|
|
||||
| 1 | GUI ist Standardstart | **erfüllt** | `PdfUmbenennerApplication.main()` → `BootstrapRunner.run()` → `StartupMode.GUI` als Default; `--headless` erforderlich für Batch-Pfad. Korrekt implementiert und getestet (`BootstrapRunnerStartupDispatchTest`). |
|
||||
| 2 | `--headless` erhalten, `--config` für beide Startarten | **erfüllt** | `CliArgumentParser` parst beide Optionen korrekt. `BootstrapRunnerConfigPathSemanticsTest` (10 Tests) prüft GUI/headless-Semantik für vorhandene und fehlende `--config`-Pfade. |
|
||||
| 3 | `.properties` als einzige Konfigurationswahrheit | **erfüllt** | `PropertiesConfigurationPortAdapter` und `GuiConfigurationPropertiesWriter` schreiben/lesen ausschließlich `.properties`. Keine zweite Konfigurationswelt. |
|
||||
| 4 | Zwei Provider (Claude, OpenAI-kompatibel), genau einer aktiv | **erfüllt** | `AiProviderSelector` wählt anhand `ai.provider.active`. `AiModelCatalogDispatcher` unterstützt beide Familien. Konfigurationsbeispiel zeigt beide Blöcke. Provider-Identifikator in Versuchshistorie persistiert (`ProviderIdentifierE2ETest`, 5 Tests). |
|
||||
| 5 | Hexagonale Architektur (keine JavaFX in Domain/Application) | **erfüllt** | Grep-Prüfung: kein `import javafx` in `domain` oder `application`. `adapter-in-cli` und `adapter-out` ebenfalls JavaFX-frei. JavaFX ausschließlich in `adapter-in-gui`. |
|
||||
| 6 | Threadingmodell (Worker für I/O, Platform.runLater für UI) | **erfüllt** | `GuiConfigurationEditorWorkspace`, `GuiModelCatalogCoordinator`, `GuiCorrectionDialogCoordinator`, `GuiTechnicalTestCoordinator` nutzen alle explizit `Platform.runLater` für UI-Updates. Worker-Thread-Trennung ist im Code nachweisbar. |
|
||||
| 7 | Naming-Regel (keine M/AP/V-Bezeichner in Code) | **erfüllt** | Grep auf `M[0-9]+`, `AP-[0-9]+`, `V[12]\.[0-9]` in allen `src/main/java` und `src/test/java` liefert keine Treffer für neue V2.0-Module (`adapter-in-gui`, `bootstrap`-Erweiterungen). |
|
||||
| 8 | JavaDoc-Standard | **erfüllt** | Alle neu hinzugefügten öffentlichen Klassen und Methoden haben Klassen- und Methoden-JavaDoc. `BootstrapRunner` und `PdfUmbenennerApplication` vollständig dokumentiert. `package-info.java` vorhanden in neuen Packages. |
|
||||
| 9 | Dirty-State, Schutzdialog, Validierung, technische Tests | **erfüllt** | `GuiDirtyStateTest`, `GuiUnsavedChangesGuardSmokeTest`, `GuiValidateActionSmokeTest`, `GuiTechnicalTestCoordinatorSmokeTest` belegen die Kernpfade. `GuiUnsavedChangesGuard` kapselt die Dirty-State-Logik. |
|
||||
| 10 | Meldungsbereich mit vier Stufen, feldnahe Fehlermeldungen | **erfüllt** | `GuiMessageSeverity` (INFO, HINWEIS, WARNUNG, FEHLER), `GuiMessageEntry` und `GuiFieldFinding` implementieren die Anforderungen. `GuiMessageAreaSmokeTest` und `GuiMessageEntryTest` prüfen sie. |
|
||||
| 11 | Modellabruf, Manueller Modellfallback, Verwerfen-Regel | **erfüllt** | `GuiModelCatalogCoordinator` implementiert automatischen Abruf bei Providerwechsel, ComboBox vs. Textfeld-Umschaltung, und Verwerfen-Regel bei Listenwechsel. `GuiModelCatalogSmokeTest` prüft die Kernpfade. |
|
||||
| 12 | Korrekturhilfen mit gesammeltem Bestätigungsdialog | **erfüllt** | `GuiCorrectionDialogCoordinator` sammelt Korrekturen und steuert den Bestätigungsdialog. `ConfirmationDialogContent` kapselt die Dialog-Inhalte. `GuiCorrectionDialogCoordinatorSmokeTest` prüft den Ablauf. |
|
||||
| 13 | Automatische Prompt-Erzeugung | **erfüllt** | `DefaultPromptTemplate.defaultContent()` in `application`-Schicht; `FilesystemResourceCreationAdapter` erzeugt die Datei. `DefaultPromptTemplateTest` und `FilesystemResourceCreationAdapterTest` prüfen Inhalt und Erzeugung. Beispiel in `docs/examples/prompt.txt` konsistent. |
|
||||
| 14 | Windows-Pfade und gemappte Laufwerke | **erfüllt** | `FilesystemPathCheckAdapter` akzeptiert Windows-Laufwerksbuchstaben. `FilesystemPathCheckAdapterTest` (28 Tests) enthält Windows-Pfad-Szenarien. Dokumentation in `betrieb.md` und `gui-bedienanleitung.md` ausdrücklich erwähnt. |
|
||||
| 15 | Legacy-Migration mit `.bak`-Sicherung | **erfüllt** | `LegacyConfigurationMigrator` in `adapter-out`; GUI-Pfad ruft `detectedLegacyConfiguration` + `migrateConfigurationIfNeeded` in `BootstrapRunner` auf. `GuiConfigurationPropertiesWriterTest` prüft Backup-Schema. |
|
||||
| 16 | Keine neuen Provider über Claude/OpenAI-kompatibel hinaus | **erfüllt** | Codebase enthält ausschließlich `ClaudeAiInvocationAdapter` und `OpenAiCompatibleAiInvocationAdapter`. Kein dritter Provider. |
|
||||
| 17 | Keine neuen Distributionsformate (EXE/Installer) | **erfüllt** | `pom.xml` des Bootstrap-Moduls nutzt ausschließlich `maven-shade-plugin`. Kein `launch4j`, kein `jpackage`, kein Installer. |
|
||||
| 18 | Kein manueller Verarbeitungslauf aus GUI | **erfüllt** | `adapter-in-gui` enthält keine Klasse, die `BatchRunProcessingUseCase` aus einem GUI-Event aufruft. Kein „Start"-Button, keine Batch-Ausführungslogik im GUI-Adapter. |
|
||||
| 19 | Keine DB-/Historienanzeige | **erfüllt** | Kein SQLite-Lesepfad aus `adapter-in-gui`. Kein Historien-Tab. Kein Ergebnis-Browser. |
|
||||
| 20 | Keine fachlichen Änderungen an Kernverarbeitung | **erfüllt** | `DefaultBatchRunProcessingUseCase`, `DocumentProcessingCoordinator`, `AiNamingService`, `AiResponseValidator` sind gegenüber dem V1.1-Freigabestand unverändert. E2E-Tests (`BatchRunEndToEndTest`, 11 Szenarien) sind alle grün. |
|
||||
|
||||
---
|
||||
|
||||
## Dokumentations-Vollständigkeitsprüfung
|
||||
|
||||
| Dokument | Status | Bewertung |
|
||||
|---|---|---|
|
||||
| `docs/betrieb.md` | vollständig | Alle V2.0-Themen abgedeckt: GUI-Standardstart, `--headless`, `--config`-Semantik für beide Modi, Plattformhinweis Windows, gemappte Laufwerke, GUI-Umfangsbegrenzung, Build- und Packaging-Abschnitt, JavaFX-Integration, headless ohne JavaFX-Initialisierung |
|
||||
| `docs/gui-bedienanleitung.md` | vollständig | Alle AP-002-Themen abgedeckt: Startzustände (Abschnitte 2.1–2.4), alle 7 Aktionen (Abschnitte 4.1–4.7), vier Meldungsstufen (Abschnitt 3.2), feldnahe Fehlermeldungen (Abschnitt 5), Provider-Bedienung und Modellabruf (Abschnitt 7), API-Key-Auflösungsreihenfolge (Abschnitt 10), Dirty-State (Abschnitt 8), `.bak`-Sicherung und Legacy-Migration (Abschnitt 9), Windows-Hinweise (Abschnitt 12), bekannte Einschränkungen (Abschnitt 13) |
|
||||
| `docs/examples/application.properties` | vollständig und konsistent | Alle Parameter des V2.0-Schemas vorhanden (beide Provider-Blöcke, alle Pflicht- und Optionalparameter). Kommentare zu Warnschwellen für `max.text.characters` enthalten. Default-Provider `claude` gesetzt (alphabetisch erster). Konsistent mit `GuiConfigurationTemplateFactory`. |
|
||||
| `docs/examples/prompt.txt` | vollständig und konsistent | Deutschsprachiger Standardprompt. Inhaltlich identisch mit dem, was `DefaultPromptTemplate.defaultContent()` erzeugt (durch `FilesystemResourceCreationAdapterTest` nachgewiesen). JSON-Schema-Anforderungen (title, reasoning, date optional) abgebildet. |
|
||||
| `README.md` | vollständig | V2.0-Hinweis im Header, GUI-Standardstart dokumentiert, `--headless` und `--config`-Beispiele vorhanden, Modul `pdf-umbenenner-adapter-in-gui` aufgelistet, Verweis auf `betrieb.md` und `gui-bedienanleitung.md`. |
|
||||
|
||||
---
|
||||
|
||||
## Release-Blocker
|
||||
|
||||
**Keine Release-Blocker identifiziert.**
|
||||
|
||||
Der vollständige Maven-Reactor-Build ist grün (1.398 Tests, 0 Failures, 0 Errors, 0 Skipped).
|
||||
Alle 20 Prüfpunkte gegen die Spec-Trias sind als erfüllt bewertet. Die Dokumentation ist
|
||||
vollständig und konsistent mit dem Code.
|
||||
|
||||
---
|
||||
|
||||
## Nicht blockierende Restpunkte
|
||||
|
||||
#### R1 – ByteBuddy-Agent-Warnung bei Tests
|
||||
|
||||
**Thema:** Testqualität / Laufzeitwarnung
|
||||
**Befund:** Beim Build erscheint wiederholt `WARNING: A Java agent has been loaded dynamically
|
||||
(byte-buddy-agent-1.14.12.jar)`. Der Hinweis stammt von Mockito und tritt seit dem V1.1-Stand auf.
|
||||
Er ist nicht neu, betrifft nur die Testlaufzeit und hat keinen funktionalen Einfluss auf das
|
||||
produzierte Artefakt.
|
||||
**Bewertung:** Kein Handlungsbedarf. Mit `-XX:+EnableDynamicAgentLoading` unterdrückbar, aber
|
||||
keine Pflicht für V2.0.
|
||||
|
||||
#### R2 – GUI-Tests ohne echten JavaFX-Rendering-Pfad
|
||||
|
||||
**Thema:** Testtiefe GUI
|
||||
**Befund:** Die GUI-Tests (`GuiAdapterSmokeTest`, `GuiEditorRegressionSmokeTest` usw.) laufen
|
||||
unter headless JavaFX (Monocle) und prüfen View-Modell-Logik, Zustandsübergänge und
|
||||
Koordinatoren-Verhalten. Das visuelle Rendering der Oberfläche (Farbgebung der Meldungspräfixe,
|
||||
Layout-Details) ist nicht automatisiert geprüft. Dies entspricht der in CLAUDE.md definierten
|
||||
GUI-Teststrategie (kein TestFX über Monocle hinaus) und ist keine Abweichung vom Ziel.
|
||||
**Bewertung:** Kein Handlungsbedarf. Entspricht der Teststrategie-Vorgabe.
|
||||
|
||||
---
|
||||
|
||||
## Bewusst außerhalb V2.0 liegende Themen (V2.1+)
|
||||
|
||||
Die folgenden Themen wurden im V2.0-Umfang nachweislich **nicht** implementiert und sind
|
||||
ausdrücklich für spätere Ausbaustufen vorgesehen:
|
||||
|
||||
- **Manueller Verarbeitungslauf aus der GUI** (V2.1+)
|
||||
- **DB-/Historienansicht** in der GUI (V2.x+)
|
||||
- **Kosten-Tracking** und Token-/Preisberechnung (V2.x+)
|
||||
- **EXE-Wrapper / Installer** (V3+)
|
||||
- **Weitere KI-Provider** über Claude und OpenAI-kompatibel hinaus (V3+)
|
||||
- **Automatischer Fallback zwischen Providern** (V3+)
|
||||
- **Profilverwaltung mit mehreren Konfigurationen je Provider** (V3+)
|
||||
- **Plattformübergreifender offizieller GUI-Support** (V3+)
|
||||
|
||||
---
|
||||
|
||||
## Gesamtbewertung V2.0-Stand
|
||||
|
||||
| Klassifikation | Anzahl | Beschreibung |
|
||||
|---|---|---|
|
||||
| Release-Blocker | **0** | – |
|
||||
| Nicht blockierende Restpunkte | **2** | R1 ByteBuddy-Warnung, R2 Testtiefe GUI-Rendering |
|
||||
| Bewusst außerhalb V2.0 | **8** | Manueller Lauf, Historienansicht, Kosten-Tracking, EXE, weitere Provider, Fallback, Profile, Cross-Platform |
|
||||
|
||||
**Build:** ERFOLGREICH · 1.398 Tests · 0 Failures · 0 Errors · Laufzeit 01:18 min
|
||||
**Alle 20 Spezifikations-Prüfpunkte:** erfüllt
|
||||
**Dokumentation:** vollständig und konsistent
|
||||
|
||||
+187
-11
@@ -8,10 +8,70 @@ und legt eine Kopie im konfigurierten Zielordner ab. Die Quelldatei bleibt unver
|
||||
|
||||
---
|
||||
|
||||
## Startmodi und Betriebsmodell (V2.0)
|
||||
|
||||
Ab V2.0 enthält die Anwendung zwei Startmodi in **einem gemeinsamen ausführbaren JAR**:
|
||||
|
||||
| Startmodus | Beschreibung |
|
||||
|---|---|
|
||||
| **GUI-Start** (Standard) | Öffnet die JavaFX-Desktop-GUI. Wird verwendet, wenn kein `--headless` angegeben ist. |
|
||||
| **headless Betrieb** | Klassischer Batch-/Scheduler-Betrieb ohne grafische Oberfläche. Wird über `--headless` aktiviert. |
|
||||
|
||||
### CLI-Optionen
|
||||
|
||||
| Option | Beschreibung |
|
||||
|---|---|
|
||||
| *(keine Argumente)* | GUI-Standardstart |
|
||||
| `--headless` | Aktiviert den headless Batch-Betrieb (wie vor V2.0) |
|
||||
| `--config <pfad>` | Zeigt explizit auf eine `.properties`-Konfigurationsdatei (für GUI und headless) |
|
||||
|
||||
`--config` und `--headless` können kombiniert werden:
|
||||
|
||||
```
|
||||
java -jar pdf-umbenenner-bootstrap-*.jar --headless --config C:\Pfad\zur\config.properties
|
||||
```
|
||||
|
||||
### Verhalten bei fehlender oder ungültiger `--config`-Datei
|
||||
|
||||
| Startmodus | Datei nicht vorhanden | Datei vorhanden, aber ungültig |
|
||||
|---|---|---|
|
||||
| **headless** | Harter Startfehler, Exit-Code `1`, kein Fallback | Harter Startfehler, Exit-Code `1` |
|
||||
| **GUI** | Fehlermeldung in der GUI, danach Verhalten wie ohne `--config` (Willkommenstext) | Fehlermeldung in der GUI, Konfiguration nicht geladen |
|
||||
|
||||
Im headless Betrieb ist ein nicht vorhandener `--config`-Pfad ein **harter Startfehler**. Ein stiller
|
||||
Fallback auf das Default-Verhalten ist in diesem Fall ausdrücklich unzulässig.
|
||||
|
||||
### Verhalten bei GUI-Startfehlern
|
||||
|
||||
Tritt vor der erfolgreichen Anzeige der grafischen Oberfläche ein nicht behebbarer Fehler auf
|
||||
(z. B. fehlende JavaFX-Laufzeit, Bootstrap-Fehler), beendet sich die Anwendung mit Exit-Code `1`.
|
||||
|
||||
### Plattform und Laufwerksbuchstaben
|
||||
|
||||
Die GUI wird **offiziell nur unter Windows** unterstützt. Der headless Betrieb bleibt für den
|
||||
Windows Server-Betrieb geeignet.
|
||||
|
||||
Gemappte Netzlaufwerke wie `S:\` oder `H:\` werden ausdrücklich unterstützt. Eine Ablehnung
|
||||
solcher Pfade allein wegen eines dahinterliegenden UNC-Pfads ist unzulässig.
|
||||
|
||||
### Umfang der V2.0-GUI
|
||||
|
||||
Die GUI in V2.0 dient ausschließlich als:
|
||||
|
||||
- **Konfigurationseditor** für die `.properties`-Datei
|
||||
- **Validierungsoberfläche** (automatische und explizite Prüfung des Konfigurationsstands)
|
||||
- **Technische Testoberfläche** (Erreichbarkeit des Providers, Pfade, SQLite-Datei, Prompt-Datei)
|
||||
|
||||
Die GUI enthält in V2.0 **keinen** manuellen Verarbeitungslauf. Das Starten eines Batch-Laufs
|
||||
aus der GUI ist erst ab V2.1+ vorgesehen. Der headless Betrieb über den Windows Task Scheduler
|
||||
bleibt der einzige Weg, PDF-Dateien automatisiert zu verarbeiten.
|
||||
|
||||
---
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- Java 21 (JRE oder JDK)
|
||||
- Zugang zu einem OpenAI-kompatiblen KI-Dienst (API-Schlüssel erforderlich)
|
||||
- Zugang zu einem KI-Dienst (API-Schlüssel erforderlich; unterstützte Provider: OpenAI-kompatibel, Anthropic Claude)
|
||||
- Quellordner mit OCR-verarbeiteten PDF-Dateien
|
||||
- Schreibzugriff auf Zielordner und Datenbankverzeichnis
|
||||
|
||||
@@ -26,16 +86,24 @@ Das ausführbare JAR wird durch den Maven-Build im Verzeichnis
|
||||
java -jar pdf-umbenenner-bootstrap/target/pdf-umbenenner-bootstrap-0.0.1-SNAPSHOT.jar
|
||||
```
|
||||
|
||||
Die Anwendung liest die Konfiguration aus `config/application.properties` relativ zum
|
||||
Ohne weitere Argumente öffnet sich die **GUI** (Standardstart ab V2.0).
|
||||
|
||||
Für den **headless Betrieb** (Batch-/Scheduler-Start):
|
||||
|
||||
```
|
||||
java -jar pdf-umbenenner-bootstrap/target/pdf-umbenenner-bootstrap-0.0.1-SNAPSHOT.jar --headless
|
||||
```
|
||||
|
||||
Die Anwendung liest die Konfiguration standardmäßig aus `config/application.properties` relativ zum
|
||||
Arbeitsverzeichnis, in dem der Befehl ausgeführt wird.
|
||||
|
||||
### Start über Windows Task Scheduler
|
||||
|
||||
Empfohlene Startsequenz für den Windows Task Scheduler:
|
||||
Empfohlene Startsequenz für den headless Betrieb über den Windows Task Scheduler:
|
||||
|
||||
1. Aktion: Programm/Skript starten
|
||||
2. Programm: `java`
|
||||
3. Argumente: `-jar C:\Pfad\zur\Installation\pdf-umbenenner-bootstrap\target\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 --headless`
|
||||
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.
|
||||
@@ -43,11 +111,26 @@ Empfohlene Startsequenz für den Windows Task Scheduler:
|
||||
> `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.
|
||||
|
||||
Alternativ kann über `--config` ein expliziter Konfigurationspfad angegeben werden:
|
||||
|
||||
```
|
||||
java -jar ... --headless --config S:\Betrieb\meine-config.properties
|
||||
```
|
||||
|
||||
> **Wichtig:** Zeigt `--config` auf eine nicht vorhandene Datei, bricht die Anwendung mit Exit-Code `1` ab.
|
||||
> Es findet kein stiller Fallback auf `config/application.properties` statt.
|
||||
|
||||
---
|
||||
|
||||
## Konfiguration
|
||||
|
||||
Die Konfiguration wird aus `config/application.properties` geladen.
|
||||
|
||||
Ein vollständiges Konfigurationsbeispiel mit allen unterstützten Parametern,
|
||||
realistischen Windows-Pfaden und erklärenden Kommentaren liegt unter:
|
||||
|
||||
- [`docs/examples/application.properties`](examples/application.properties)
|
||||
|
||||
Vorlagen für lokale und Test-Konfigurationen befinden sich in:
|
||||
|
||||
- `config/application-local.example.properties`
|
||||
@@ -155,16 +238,17 @@ Der Dateiname der Prompt-Datei dient als Prompt-Identifikator in der Versuchshis
|
||||
(SQLite) und ermöglicht so die Nachvollziehbarkeit, welche Prompt-Version für welchen
|
||||
Verarbeitungsversuch verwendet wurde.
|
||||
|
||||
Eine Vorlage befindet sich in `config/prompts/template.txt` und kann direkt verwendet oder
|
||||
an den jeweiligen KI-Dienst angepasst werden.
|
||||
Eine angepasste Vorlage befindet sich in `config/prompts/template.txt` und kann direkt
|
||||
verwendet oder an den jeweiligen KI-Dienst angepasst werden.
|
||||
|
||||
Fehlt die konfigurierte Prompt-Datei, erzeugt die GUI beim Ausführen der technischen Tests
|
||||
automatisch eine deutsche Standardvorlage am konfigurierten Pfad. Ein Beispiel dieser
|
||||
Standardvorlage liegt unter [`docs/examples/prompt.txt`](examples/prompt.txt).
|
||||
|
||||
Die Anwendung ergänzt den Prompt automatisch um:
|
||||
- einen Dokumenttext-Abschnitt
|
||||
- eine explizite JSON-Antwortspezifikation mit den Feldern `title`, `reasoning` und `date`
|
||||
|
||||
Der Prompt in `template.txt` muss deshalb **keine** JSON-Formatanweisung enthalten –
|
||||
nur den inhaltlichen Auftrag an die KI.
|
||||
|
||||
---
|
||||
|
||||
## Zielformat
|
||||
@@ -279,11 +363,103 @@ Sie muss nicht manuell verwaltet werden – das Schema wird beim Start automatis
|
||||
|
||||
---
|
||||
|
||||
## Build und Packaging
|
||||
|
||||
### Gemeinsames ausführbares JAR
|
||||
|
||||
Die gesamte Anwendung wird als **ein einziges ausführbares JAR** ausgeliefert, das GUI-Start
|
||||
und headless Batch-Betrieb vereint. Eine separate JavaFX-Installation ist nicht erforderlich.
|
||||
|
||||
Das JAR wird vom Maven-Shade-Plugin im Modul `pdf-umbenenner-bootstrap` erzeugt.
|
||||
Nach einem erfolgreichen Build liegt es unter:
|
||||
|
||||
```
|
||||
pdf-umbenenner-bootstrap/target/pdf-umbenenner-bootstrap-0.0.1-SNAPSHOT.jar
|
||||
```
|
||||
|
||||
Dieses JAR enthält alle Abhängigkeiten inklusive der JavaFX-Plattformbibliotheken
|
||||
für Windows (Classifier `win`). Die nativen JavaFX-DLLs werden beim GUI-Start
|
||||
von JavaFX selbst in ein temporäres Verzeichnis extrahiert.
|
||||
|
||||
### Integrierte JavaFX-Laufzeit
|
||||
|
||||
JavaFX ist als Maven-Dependency im Modul `pdf-umbenenner-adapter-in-gui` mit
|
||||
Windows-Classifier deklariert (`javafx-base:win`, `javafx-graphics:win`,
|
||||
`javafx-controls:win`). Das Shade-JAR schließt diese Bibliotheken ein, sodass
|
||||
der GUI-Start ohne separate JavaFX-Installation auf dem Zielsystem funktioniert.
|
||||
|
||||
Nur das Modul `pdf-umbenenner-adapter-in-gui` hängt direkt von JavaFX ab.
|
||||
Die Module `domain`, `application`, `adapter-in-cli` und `adapter-out` sind
|
||||
vollständig JavaFX-frei.
|
||||
|
||||
### Headless-Start ohne JavaFX-Initialisierung
|
||||
|
||||
Beim headless Start (`--headless`) wird JavaFX **nicht** initialisiert. Der
|
||||
`GuiAdapter` wird nur dann instanziiert und gestartet, wenn der Startmodus GUI ist.
|
||||
JavaFX-Klassen sind zwar im Shade-JAR enthalten, werden im headless Pfad jedoch
|
||||
nicht geladen. Headless läuft damit auch auf Windows Server-Systemen ohne
|
||||
JavaFX-fähige Grafiklaufzeit.
|
||||
|
||||
### Keine EXE, kein Installer
|
||||
|
||||
In V2.0 wird ausschließlich das JAR als Distributionsartefakt ausgeliefert.
|
||||
EXE-Wrapper und Installer sind bewusst nicht Bestandteil von V2.0.
|
||||
|
||||
### Build-Kommandos
|
||||
|
||||
**Vollständiger Reactor-Build** (alle Module, Tests, Packaging):
|
||||
|
||||
```powershell
|
||||
.\mvnw.cmd clean verify
|
||||
```
|
||||
|
||||
Auf Unix-Systemen (headless CI):
|
||||
|
||||
```bash
|
||||
./mvnw clean verify
|
||||
```
|
||||
|
||||
**Nur das ausführbare JAR erzeugen** (überspringt Tests):
|
||||
|
||||
```powershell
|
||||
.\mvnw.cmd clean package -pl pdf-umbenenner-bootstrap --also-make -DskipTests
|
||||
```
|
||||
|
||||
**Selektiver Reactor-Build** (ohne Coverage-Modul, z. B. während der Entwicklung):
|
||||
|
||||
```powershell
|
||||
.\mvnw.cmd clean verify -pl pdf-umbenenner-domain,pdf-umbenenner-application,pdf-umbenenner-adapter-out,pdf-umbenenner-adapter-in-cli,pdf-umbenenner-adapter-in-gui,pdf-umbenenner-bootstrap --also-make
|
||||
```
|
||||
|
||||
### Technische Hinweise zum Shade-JAR
|
||||
|
||||
- Signaturdateien (`*.SF`, `*.DSA`, `*.RSA`) signierter JARs (u. a. JavaFX) werden
|
||||
beim Shading entfernt, da sie im zusammengeführten JAR ungültig wären.
|
||||
- JPMS-Moduldeskriptoren (`module-info.class`) werden entfernt, da JavaFX als
|
||||
modulares Framework mit dem nicht-modularen Fat-JAR-Modell kollidieren würde.
|
||||
- `META-INF/services`-Einträge aus allen Abhängigkeiten werden durch den
|
||||
`ServicesResourceTransformer` zusammengeführt statt überschrieben.
|
||||
- Der Main-Class-Eintrag im Manifest verweist auf
|
||||
`de.gecheckt.pdf.umbenenner.bootstrap.PdfUmbenennerApplication`.
|
||||
Diese Klasse erweitert bewusst **nicht** `javafx.application.Application`,
|
||||
um den JavaFX-Modul-System-Launcher-Check zu umgehen, der Fat-JAR-Ausführung
|
||||
blockieren würde. Der GUI-Pfad ruft `Application.launch(...)` explizit auf.
|
||||
|
||||
---
|
||||
|
||||
## Weitere Dokumentation
|
||||
|
||||
Die Bedienung der GUI ist in [`gui-bedienanleitung.md`](gui-bedienanleitung.md) beschrieben.
|
||||
|
||||
---
|
||||
|
||||
## Systemgrenzen
|
||||
|
||||
- Nur OCR-verarbeitete, durchsuchbare PDF-Dateien werden verarbeitet
|
||||
- Keine eingebaute OCR-Funktion
|
||||
- Kein Web-UI, keine REST-API, keine interaktive Bedienung
|
||||
- Kein interner Scheduler – der Start erfolgt extern (z. B. Windows Task Scheduler)
|
||||
- Kein Web-UI, keine REST-API
|
||||
- Die GUI (V2.0) dient der Konfiguration, Validierung und technischen Diagnose – **kein** manueller Verarbeitungslauf aus der GUI
|
||||
- Kein interner Scheduler – der Batch-Betrieb wird extern angestoßen (z. B. Windows Task Scheduler, `--headless`)
|
||||
- Quelldateien werden nie überschrieben, verschoben oder gelöscht
|
||||
- Die Identifikation erfolgt über SHA-256-Fingerprint des Dateiinhalts, nicht über Dateinamen
|
||||
- Die GUI wird offiziell nur unter Windows unterstützt; der headless Betrieb ist für Windows Server geeignet
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
# PDF Umbenenner – vollstaendiges Konfigurationsbeispiel (V2.0)
|
||||
#
|
||||
# Diese Datei zeigt alle unterstuetzten Konfigurationsparameter mit realistischen
|
||||
# Windows-Pfaden und erklaerenden Kommentaren.
|
||||
#
|
||||
# Fuer den produktiven Einsatz: Datei nach config/application.properties kopieren
|
||||
# und Werte anpassen. Der headless Batch-Betrieb liest standardmaessig
|
||||
# config/application.properties relativ zum Arbeitsverzeichnis.
|
||||
#
|
||||
# Die GUI schlaegt beim "Speichern unter" denselben Pfad vor.
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Pfade
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Quellordner: Ordner, aus dem OCR-verarbeitete PDF-Dateien gelesen werden.
|
||||
# Der Ordner muss vorhanden und lesbar sein.
|
||||
# Beispiel: gemapptes Netzlaufwerk (wird ausdruecklich unterstuetzt)
|
||||
source.folder=S:\\Eingang
|
||||
|
||||
# Zielordner: Ordner, in den die umbenannten Kopien abgelegt werden.
|
||||
# Wird automatisch angelegt, wenn er noch nicht existiert (Schreibzugriff erforderlich).
|
||||
target.folder=S:\\Archiv
|
||||
|
||||
# SQLite-Datenbankdatei fuer Bearbeitungsstatus und Versuchshistorie.
|
||||
# Das uebergeordnete Verzeichnis muss vorhanden sein.
|
||||
sqlite.file=S:\\Archiv\\pdf-umbenenner.db
|
||||
|
||||
# Pfad zur externen Prompt-Datei. Der Dateiname dient als Prompt-Identifikator
|
||||
# in der Versuchshistorie und ermoeg licht die Nachvollziehbarkeit der verwendeten
|
||||
# Prompt-Version. Fehlt die Datei, kann die GUI sie automatisch anlegen (deutsche
|
||||
# Standardvorlage). Ein Beispiel der Standardvorlage liegt unter docs/examples/prompt.txt.
|
||||
prompt.template.file=S:\\Archiv\\prompt.txt
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Aktiver KI-Provider
|
||||
# ---------------------------------------------------------------------------
|
||||
# Genau ein Provider ist aktiv. Kein automatischer Fallback, keine parallele Nutzung.
|
||||
# Erlaubte Werte: claude, openai-compatible
|
||||
#
|
||||
# Hinweis: Die GUI-Standardvorlage ("Neu") setzt standardmaessig "claude" als aktiven
|
||||
# Provider, weil Claude alphabetisch der erste unterstuetzte Provider ist.
|
||||
ai.provider.active=claude
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Provider: Anthropic Claude
|
||||
# ---------------------------------------------------------------------------
|
||||
# Wird verwendet, wenn ai.provider.active=claude gesetzt ist.
|
||||
|
||||
# Basis-URL des Anthropic-Dienstes (Standard: https://api.anthropic.com)
|
||||
ai.provider.claude.baseUrl=https://api.anthropic.com
|
||||
|
||||
# Modellname (z. B. claude-3-5-sonnet-20241022)
|
||||
ai.provider.claude.model=claude-3-5-sonnet-20241022
|
||||
|
||||
# HTTP-Timeout fuer KI-Anfragen in Sekunden (muss > 0 sein).
|
||||
ai.provider.claude.timeoutSeconds=60
|
||||
|
||||
# API-Schluessel fuer Anthropic.
|
||||
# Vorrangreihenfolge: Umgebungsvariable ANTHROPIC_API_KEY > dieser Wert.
|
||||
# Das Feld darf leer bleiben, wenn die Umgebungsvariable gesetzt ist.
|
||||
ai.provider.claude.apiKey=
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Provider: OpenAI-kompatibel
|
||||
# ---------------------------------------------------------------------------
|
||||
# Wird verwendet, wenn ai.provider.active=openai-compatible gesetzt ist.
|
||||
# Geeignet fuer OpenAI selbst und jeden API-kompatiblen Drittanbieter.
|
||||
|
||||
# Basis-URL des KI-Dienstes (ohne Pfadsuffix wie /chat/completions).
|
||||
ai.provider.openai-compatible.baseUrl=https://api.openai.com/v1
|
||||
|
||||
# Modellname (z. B. gpt-4o-mini)
|
||||
ai.provider.openai-compatible.model=gpt-4o-mini
|
||||
|
||||
# HTTP-Timeout fuer KI-Anfragen in Sekunden (muss > 0 sein).
|
||||
ai.provider.openai-compatible.timeoutSeconds=30
|
||||
|
||||
# API-Schluessel fuer OpenAI-kompatible Dienste.
|
||||
# Vorrangreihenfolge: OPENAI_COMPATIBLE_API_KEY (Umgebungsvariable) >
|
||||
# PDF_UMBENENNER_API_KEY (veraltete Umgebungsvariable, weiterhin akzeptiert) >
|
||||
# ai.provider.openai-compatible.apiKey (dieser Wert)
|
||||
# Das Feld darf leer bleiben, wenn die Umgebungsvariable gesetzt ist.
|
||||
ai.provider.openai-compatible.apiKey=
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Verarbeitungslimits
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Maximale Anzahl historisierter transienter Fehlversuche pro Dokument.
|
||||
# Muss eine ganze Zahl >= 1 sein. Wert 0 ist ungueltige Konfiguration.
|
||||
max.retries.transient=3
|
||||
|
||||
# Maximale Seitenzahl pro Dokument. Dokumente mit mehr Seiten werden als
|
||||
# deterministischer Inhaltsfehler behandelt (kein KI-Aufruf).
|
||||
max.pages=10
|
||||
|
||||
# Maximale Zeichenanzahl des Dokumenttexts, der an die KI gesendet wird.
|
||||
# Werte bis 1000: unkritisch.
|
||||
# Werte 1001-3000: erhoehte KI-Kosten moeglich (Warnung in der GUI).
|
||||
# Werte ab 3001: deutlich erhoehte KI-Kosten moeglich (starke Warnung in der GUI).
|
||||
# Standardvorlage der GUI: 5000.
|
||||
max.text.characters=5000
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Optionale Parameter
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Lock-Datei fuer den Startschutz (verhindert parallele Instanzen).
|
||||
# Ohne Konfiguration: pdf-umbenenner.lock im Arbeitsverzeichnis.
|
||||
runtime.lock.file=S:\\Archiv\\pdf-umbenenner.lock
|
||||
|
||||
# Log-Verzeichnis. Ohne Konfiguration: ./logs/ im Arbeitsverzeichnis.
|
||||
log.directory=S:\\Archiv\\logs
|
||||
|
||||
# Log-Level (DEBUG, INFO, WARN, ERROR). Standard: INFO.
|
||||
log.level=INFO
|
||||
|
||||
# Sensible KI-Inhalte (vollstaendige Rohantwort und Reasoning) ins Log schreiben.
|
||||
# Erlaubte Werte: true oder false. Standard: false (geschuetzt).
|
||||
# Die KI-Rohantwort wird unabhaengig davon immer in der SQLite-Datenbank gespeichert.
|
||||
log.ai.sensitive=false
|
||||
@@ -0,0 +1,23 @@
|
||||
Du bist ein Assistent für ein deutsches Dokumentenverwaltungssystem.
|
||||
Deine Aufgabe ist es, aus dem Inhalt einer bereits OCR-verarbeiteten PDF-Datei
|
||||
einen aussagekräftigen, kurzen und normierten Dateinamensvorschlag zu erstellen.
|
||||
|
||||
Antworte ausschließlich mit einem validen JSON-Objekt im folgenden Schema:
|
||||
{
|
||||
"date": "YYYY-MM-DD",
|
||||
"title": "Kurztitel auf Deutsch",
|
||||
"reasoning": "Kurze Begründung auf Deutsch"
|
||||
}
|
||||
|
||||
Regeln:
|
||||
- Das Feld "title" ist verpflichtend.
|
||||
- Das Feld "reasoning" ist verpflichtend.
|
||||
- Das Feld "date" ist optional. Wenn kein belastbares Datum aus dem Dokument eindeutig ableitbar ist, lass das Feld weg. Kein Datum erfinden.
|
||||
- Das Datumsformat ist YYYY-MM-DD (z.B. 2026-03-15).
|
||||
- Der Titel ist auf Deutsch, verständlich und eindeutig für den Dokumentinhalt.
|
||||
- Der Titel hat maximal 20 Zeichen (Basistitel ohne Suffix).
|
||||
- Keine generischen Bezeichner wie "Dokument", "Scan", "Datei", "PDF".
|
||||
- Keine Sonderzeichen außer Leerzeichen im Titel.
|
||||
- Eigennamen bleiben unverändert.
|
||||
- Umlaute und ß sind erlaubt.
|
||||
- Kein Text außerhalb des JSON-Objekts.
|
||||
@@ -0,0 +1,94 @@
|
||||
# V2.0-Freigabe
|
||||
|
||||
## Geprüfter Stand
|
||||
|
||||
- Git-Branch: `main`
|
||||
- Git-Commit (HEAD, zum Zeitpunkt der Prüfung): `1bb7a427357c73039c09a8e1bfe351dee54df765`
|
||||
- Datum der Prüfung: 2026-04-20
|
||||
|
||||
---
|
||||
|
||||
## Ausgeführte Prüfungen
|
||||
|
||||
| Prüfung | Ergebnis |
|
||||
|---|---|
|
||||
| Vollständiger Maven-Reactor-Build (`clean verify`, alle 6 Module, `-DskipPitest=true`) | **ERFOLGREICH** |
|
||||
| Unit-Tests gesamt | **1.398 Tests, 0 Failures, 0 Errors, 0 Skipped** |
|
||||
| Integrations-/Smoke-Tests (`ExecutableJarSmokeTestIT`, Bootstrap) | **5 Tests, alle grün** |
|
||||
| Shaded-JAR erzeugt unter `pdf-umbenenner-bootstrap/target/` | **ja** |
|
||||
| Konfigurations- und Dokumentationsbeispiele auf Konsistenz geprüft | **ja** |
|
||||
| Bedienanleitung (`gui-bedienanleitung.md`) gegen reales GUI-Verhalten stichprobengeprüft | **ja – ein während der Prüfung identifizierter Befund wurde unmittelbar korrigiert (siehe R3)** |
|
||||
|
||||
---
|
||||
|
||||
## Build- und Test-Ergebnisse
|
||||
|
||||
Ausgeführtes Kommando:
|
||||
```
|
||||
.\mvnw.cmd clean verify -pl pdf-umbenenner-domain,pdf-umbenenner-application,pdf-umbenenner-adapter-out,pdf-umbenenner-adapter-in-cli,pdf-umbenenner-adapter-in-gui,pdf-umbenenner-bootstrap --also-make -DskipPitest=true
|
||||
```
|
||||
|
||||
**Gesamtergebnis: BUILD SUCCESS**
|
||||
**Laufzeit: 01:15 min**
|
||||
|
||||
| Modul | Tests | Failures | Errors | Skipped |
|
||||
|---|---|---|---|---|
|
||||
| `pdf-umbenenner-domain` | 227 | 0 | 0 | 0 |
|
||||
| `pdf-umbenenner-application` | 455 | 0 | 0 | 0 |
|
||||
| `pdf-umbenenner-adapter-in-cli` | 8 | 0 | 0 | 0 |
|
||||
| `pdf-umbenenner-adapter-in-gui` | 190 | 0 | 0 | 0 |
|
||||
| `pdf-umbenenner-adapter-out` | 371 | 0 | 0 | 0 |
|
||||
| `pdf-umbenenner-bootstrap` (Unit) | 147 | 0 | 0 | 0 |
|
||||
| `pdf-umbenenner-bootstrap` (IT) | 5 | 0 | 0 | 0 |
|
||||
| **Gesamt** | **1.403** | **0** | **0** | **0** |
|
||||
|
||||
Erzeugtes Shaded-JAR: `pdf-umbenenner-bootstrap/target/pdf-umbenenner-bootstrap-0.0.1-SNAPSHOT.jar`
|
||||
Größe: ca. 28 MB (enthält JavaFX für Windows, PDFBox, SQLite-JDBC, Log4j2 und alle Module)
|
||||
|
||||
---
|
||||
|
||||
## Offene nicht blockierende Restpunkte
|
||||
|
||||
Die folgenden Restpunkte wurden in der integrierten Gesamtprüfung dokumentiert und
|
||||
gelten als freigabekompatibel.
|
||||
|
||||
### R1 – ByteBuddy-Agent-Warnung bei Tests
|
||||
|
||||
Beim Build erscheint wiederholt `WARNING: A Java agent has been loaded dynamically
|
||||
(byte-buddy-agent-1.14.12.jar)`. Der Hinweis stammt von Mockito und tritt seit dem V1.1-Stand
|
||||
auf. Kein funktionaler Einfluss. Mit `-XX:+EnableDynamicAgentLoading` unterdrückbar, aber keine
|
||||
Pflicht für V2.0.
|
||||
|
||||
### R2 – GUI-Tests ohne echten JavaFX-Rendering-Pfad
|
||||
|
||||
Die GUI-Tests laufen unter headless JavaFX (Monocle) und prüfen View-Modell-Logik,
|
||||
Zustandsübergänge und Koordinatoren-Verhalten. Das visuelle Rendering (Farbgebung der
|
||||
Meldungspräfixe, Layout-Details) ist nicht automatisiert geprüft. Dies entspricht der in
|
||||
CLAUDE.md definierten GUI-Teststrategie (kein TestFX über Monocle hinaus).
|
||||
|
||||
### R3 – Bedienanleitung: Legacy-Umgebungsvariable für OpenAI-kompatibel (behoben)
|
||||
|
||||
**Ursprünglicher Befund:** Abschnitt 10 der `gui-bedienanleitung.md` beschrieb
|
||||
`OPENAI_COMPATIBLE_API_KEY` fälschlich als Legacy-Umgebungsvariable. Tatsächlich ist
|
||||
`OPENAI_COMPATIBLE_API_KEY` die primäre providerspezifische Umgebungsvariable, und die
|
||||
echte Legacy-Umgebungsvariable lautet `PDF_UMBENENNER_API_KEY`.
|
||||
|
||||
**Status:** Korrigiert. Abschnitt 10 benennt jetzt `OPENAI_COMPATIBLE_API_KEY` als
|
||||
primäre Variable und `PDF_UMBENENNER_API_KEY` als Legacy-Variable und hält fest, dass
|
||||
Claude keine Legacy-Variable hat.
|
||||
|
||||
**Kein Release-Blocker und im V2.0-Freigabestand nicht mehr offen.**
|
||||
|
||||
---
|
||||
|
||||
## Freigabeaussage
|
||||
|
||||
V2.0 ist nach Prüfung fehlerfrei buildbar, vollständig nach Spezifikation umgesetzt
|
||||
und als freigabefähig einzustufen. Keine Release-Blocker. Die zwei nicht blockierenden
|
||||
Restpunkte (R1, R2) sind dokumentiert und können außerhalb des V2.0-Scopes adressiert
|
||||
werden; R3 wurde während der finalen Prüfung unmittelbar behoben.
|
||||
|
||||
Der vollständige Maven-Reactor-Build ist grün (1.403 Tests, 0 Failures, 0 Errors,
|
||||
0 Skipped). Alle in der integrierten Gesamtprüfung definierten Spezifikations-
|
||||
Prüfpunkte gegen die Spec-Trias sind als erfüllt bestätigt. Die Dokumentation ist
|
||||
vollständig und konsistent mit dem Code.
|
||||
@@ -0,0 +1,405 @@
|
||||
# GUI-Bedienanleitung – PDF-Umbenenner V2.0
|
||||
|
||||
Diese Anleitung beschreibt die JavaFX-Desktop-GUI des PDF-Umbenenners. Sie richtet sich an
|
||||
Endbenutzer und Betreuer, die die Konfiguration der Anwendung über die grafische Oberfläche
|
||||
verwalten und technisch prüfen möchten.
|
||||
|
||||
---
|
||||
|
||||
## 1. Zweck und Scope der GUI in V2.0
|
||||
|
||||
Die GUI dient in V2.0 ausschließlich als:
|
||||
|
||||
- **Konfigurationseditor** für die `.properties`-Datei
|
||||
- **Validierungsoberfläche** für den aktuellen Konfigurationsstand
|
||||
- **Technische Test- und Diagnoseoberfläche** für Erreichbarkeit des Providers,
|
||||
Pfadprüfungen und Ressourcenverfügbarkeit
|
||||
|
||||
Die GUI enthält in V2.0 **keinen** manuellen Verarbeitungslauf. Das Starten eines
|
||||
Batch-Laufs aus der GUI ist erst ab einer späteren Ausbaustufe vorgesehen.
|
||||
Ebenso gibt es keinen Historien-Tab, keine Datenbankansicht und kein Kosten-Tracking.
|
||||
|
||||
Der headless Batch-/Scheduler-Betrieb über `--headless` bleibt der einzige Weg,
|
||||
PDF-Dateien automatisiert zu verarbeiten.
|
||||
|
||||
---
|
||||
|
||||
## 2. Startzustände
|
||||
|
||||
### 2.1 GUI-Standardstart
|
||||
|
||||
Wird die Anwendung ohne CLI-Argumente gestartet, öffnet sich die JavaFX-Desktop-GUI.
|
||||
Es wird keine Konfigurationsdatei automatisch geladen.
|
||||
|
||||
Stattdessen zeigt die GUI einen deutschen Willkommenstext mit dem Hinweis, über
|
||||
„Neu" eine Standardvorlage zu erzeugen oder über „Öffnen" eine bestehende
|
||||
`.properties`-Datei zu laden.
|
||||
|
||||
### 2.2 Start mit `--config <pfad>` (gültige Datei)
|
||||
|
||||
Wird beim Start eine gültige `.properties`-Datei über `--config <pfad>` angegeben,
|
||||
lädt die GUI diese Datei automatisch und zeigt den Inhalt im Editor an. Der Pfad
|
||||
wird im Header angezeigt.
|
||||
|
||||
### 2.3 Start mit `--config <pfad>` (ungültiger oder nicht vorhandener Pfad)
|
||||
|
||||
Zeigt der angegebene Pfad auf eine nicht vorhandene oder nicht lesbare Datei,
|
||||
erscheint eine Fehlermeldung im zentralen Meldungsbereich. Danach verhält sich die
|
||||
GUI so, als wäre `--config` nicht angegeben worden: Es erscheint der Willkommenstext,
|
||||
und der Benutzer kann manuell eine Datei öffnen oder eine neue Konfiguration anlegen.
|
||||
|
||||
Im Unterschied zum headless Betrieb ist dies kein harter Startfehler – die GUI
|
||||
bleibt vollständig bedienbar.
|
||||
|
||||
### 2.4 GUI-Startfehler vor Anzeige der Oberfläche
|
||||
|
||||
Tritt vor der erfolgreichen Anzeige der grafischen Oberfläche ein nicht behebbarer
|
||||
Fehler auf (z. B. fehlende JavaFX-Laufzeit, schwerwiegender Bootstrap-Fehler),
|
||||
beendet sich die Anwendung mit Exit-Code `1`. In diesem Fall wird keine Oberfläche
|
||||
angezeigt.
|
||||
|
||||
---
|
||||
|
||||
## 3. Header und Meldungsbereich
|
||||
|
||||
### 3.1 Header
|
||||
|
||||
Der Header oben im Fenster zeigt den Pfad der aktuell geladenen `.properties`-Datei.
|
||||
Ist keine Datei geladen, bleibt der Pfadbereich leer oder zeigt einen entsprechenden
|
||||
Hinweis.
|
||||
|
||||
Sobald ungespeicherte Änderungen vorliegen, erscheinen zwei visuelle Markierungen:
|
||||
|
||||
- ein kleines **„geändert"**-Label im Header
|
||||
- ein führendes **`*`** im Fenstertitel
|
||||
|
||||
Diese Markierungen verschwinden, sobald die Datei gespeichert wurde oder Änderungen
|
||||
verworfen werden.
|
||||
|
||||
### 3.2 Zentraler Meldungsbereich
|
||||
|
||||
Am unteren Ende der GUI befindet sich ein großer, nicht editierbarer
|
||||
Meldungsbereich. Er ist dauerhaft sichtbar und zeigt Ergebnisse von
|
||||
Validierungen, technischen Tests, Migrationsmeldungen und sonstige
|
||||
Statusinformationen.
|
||||
|
||||
Der Meldungsbereich verwendet vier feste Stufen:
|
||||
|
||||
| Stufe | Präfix | Farbe des Präfix |
|
||||
|-------|--------|------------------|
|
||||
| **Info** | `Info:` | Blau |
|
||||
| **Hinweis** | `Hinweis:` | Grün |
|
||||
| **Warnung** | `Warnung:` | Orange |
|
||||
| **Fehler** | `Fehler:` | Rot |
|
||||
|
||||
Nur das Präfix am Zeilenanfang wird farbig dargestellt. Der eigentliche
|
||||
Meldungstext derselben Zeile ist immer schwarz. Die vier Stufen dienen
|
||||
ausschließlich der visuellen Einordnung; sie verhindern das Speichern nicht.
|
||||
|
||||
---
|
||||
|
||||
## 4. Aktionen
|
||||
|
||||
### 4.1 Neu
|
||||
|
||||
Erzeugt eine neue Konfiguration aus der internen Standardvorlage. Die Vorlage
|
||||
enthält sinnvolle Standardwerte und beide bekannten Provider-Blöcke (Claude und
|
||||
OpenAI-kompatibel). Standardmäßig ist der alphabetisch erste Provider (Claude)
|
||||
als aktiver Provider vorbelegt.
|
||||
|
||||
Sind zum Zeitpunkt der Aktion ungespeicherte Änderungen vorhanden, erscheint
|
||||
der Schutzdialog (siehe Abschnitt 8).
|
||||
|
||||
### 4.2 Öffnen
|
||||
|
||||
Öffnet einen nativen Dateidialog gefiltert auf `*.properties`-Dateien. Die
|
||||
ausgewählte Datei wird geladen und im Editor angezeigt.
|
||||
|
||||
Enthält die Datei das ältere Legacy-Format (flache `api.*`-Schlüssel), wird sie
|
||||
automatisch ins aktuelle Mehrprovider-Schema migriert. Vor der Migration wird eine
|
||||
`.bak`-Sicherung der Originaldatei angelegt (siehe Abschnitt 9). Die durchgeführte
|
||||
Migration wird im zentralen Meldungsbereich sichtbar gemeldet.
|
||||
|
||||
Sind zum Zeitpunkt der Aktion ungespeicherte Änderungen vorhanden, erscheint
|
||||
der Schutzdialog (siehe Abschnitt 8).
|
||||
|
||||
### 4.3 Speichern
|
||||
|
||||
Schreibt den aktuellen Editorstand in die zuletzt geladene oder gespeicherte Datei.
|
||||
|
||||
Ist die Konfiguration neu und wurde noch nie gespeichert, verhält sich „Speichern"
|
||||
wie „Speichern unter": Es wird ein Dateidialog geöffnet und ein Speicherpfad
|
||||
erfragt.
|
||||
|
||||
### 4.4 Speichern unter
|
||||
|
||||
Öffnet einen nativen Dateidialog gefiltert auf `*.properties`-Dateien. Als
|
||||
Vorschlagspfad wird `config/application.properties` relativ zum aktuellen
|
||||
Arbeitsverzeichnis verwendet. Damit ist die gespeicherte Datei ohne weitere
|
||||
Schritte für den nächsten headless Batch-Lauf nutzbar.
|
||||
|
||||
Zeigt der Dialog auf eine bereits existierende Datei, erscheint eine
|
||||
Rückfrage „Datei überschreiben?". Bei Bestätigung wird vor dem Überschreiben
|
||||
automatisch eine `.bak`-Sicherung angelegt (siehe Abschnitt 9).
|
||||
|
||||
Kommentare und Schlüsselreihenfolge der `.properties`-Datei werden beim
|
||||
Speichern normalisiert.
|
||||
|
||||
### 4.5 Validieren
|
||||
|
||||
Führt eine explizite, nicht-schreibende Gesamtprüfung des aktuellen Editorzustands
|
||||
durch. Die Prüfung läuft lokal ohne Netzwerkzugriff und umfasst dieselben
|
||||
Regelprüfungen wie die automatische Hintergrundvalidierung, kann aber zusätzliche
|
||||
lokale Prüfpunkte zusammenführen.
|
||||
|
||||
Die Prüfung arbeitet auf dem **aktuellen GUI-Zustand**, also auch auf ungespeicherten
|
||||
Änderungen. Die Datei wird dabei **nicht** implizit gespeichert.
|
||||
|
||||
Ergebnisse erscheinen im zentralen Meldungsbereich und als feldnahe
|
||||
Fehlermeldungen direkt unter den betroffenen Eingabefeldern.
|
||||
|
||||
### 4.6 Technische Tests ausführen
|
||||
|
||||
Führt eine umfassende technische Prüfung des aktuellen Editorzustands durch,
|
||||
einschließlich provider-naher Tests mit Netzwerkzugriff. Alle Prüfpunkte werden
|
||||
vollständig und gesammelt durchlaufen; die Aktion bricht bei einem Einzelfehler
|
||||
nicht ab.
|
||||
|
||||
Geprüft werden unter anderem:
|
||||
|
||||
- `.properties`-Datei und Provider-Konfiguration
|
||||
- Erreichbarkeit von Base-URL bzw. Endpoint
|
||||
- Vorhandensein und technische Akzeptanz des API-Keys
|
||||
- Abrufbarkeit der Modellliste
|
||||
- Plausibilität des gewählten Modells
|
||||
- Vorhandensein und Lesbarkeit der Prompt-Datei
|
||||
- Quellordner (vorhanden und lesbar)
|
||||
- Zielordner (vorhanden oder anlegbar und beschreibbar)
|
||||
- SQLite-Datei bzw. -Pfad
|
||||
|
||||
Die Tests arbeiten auf dem **aktuellen GUI-Zustand** ohne implizites Speichern.
|
||||
Wenn ungespeicherte Änderungen vorliegen, ist ein entsprechender Hinweis
|
||||
zweckmäßig.
|
||||
|
||||
Bestimmte Befunde können durch korrigierende Maßnahmen behoben werden (z. B.
|
||||
Zielordner anlegen, SQLite-Datei anlegen, fehlende Prompt-Datei mit einem
|
||||
deutschen Standardinhalt erzeugen). Diese Korrekturen erfolgen **nicht** still im
|
||||
Hintergrund, sondern erst nach Bestätigung eines gesammelten Bestätigungsdialogs
|
||||
(„Folgende Korrekturen werden durchgeführt … Fortfahren?").
|
||||
|
||||
Die automatisch erzeugte Prompt-Datei enthält einen deutschen Standardprompt,
|
||||
der ohne weitere Anpassung funktioniert. Ein Beispiel dieses Standardprompts liegt
|
||||
unter [`docs/examples/prompt.txt`](../docs/examples/prompt.txt).
|
||||
|
||||
Nicht automatisch korrigierbar sind insbesondere: falscher API-Key,
|
||||
unerreichbare Base-URL, nicht verfügbare Modellliste, sonstige externe
|
||||
technische Fehler.
|
||||
|
||||
### 4.7 Modelle neu laden
|
||||
|
||||
Ruft die Modellliste des aktuell ausgewählten Providers erneut über einen
|
||||
Hintergrund-Worker-Thread ab. Der gleiche Abruf wird auch automatisch bei jedem
|
||||
Providerwechsel ausgelöst.
|
||||
|
||||
---
|
||||
|
||||
## 5. Feldnahe Fehlermeldungen
|
||||
|
||||
Direkt unter bestimmten Eingabefeldern kann die GUI kleine, rote,
|
||||
deutschsprachige Hinweise einblenden, wenn der eingetragene Wert fehlerhaft oder
|
||||
riskant ist. Diese feldnahen Meldungen ergänzen den zentralen Meldungsbereich
|
||||
und ersetzen ihn nicht.
|
||||
|
||||
Feldnahe Meldungen erscheinen nach einer Validierung oder nach dem Ausführen der
|
||||
technischen Tests. Sie verschwinden, sobald der Fehler behoben und eine neue
|
||||
Prüfung durchgeführt wurde.
|
||||
|
||||
---
|
||||
|
||||
## 6. Automatische Hintergrundvalidierung
|
||||
|
||||
Eine geladene Konfiguration wird sofort beim Öffnen geprüft. Während der
|
||||
Bearbeitung aktualisiert die Validierung ihre Ergebnisse kontinuierlich im
|
||||
Hintergrund. Ergebnisse werden im zentralen Meldungsbereich und als feldnahe
|
||||
Hinweise angezeigt.
|
||||
|
||||
Die automatische Validierung unterscheidet:
|
||||
|
||||
- **Fehler:** Der Konfigurationsstand ist nicht lauffähig.
|
||||
- **Warnungen:** Die Einstellung ist technisch akzeptabel, aber riskant oder
|
||||
unüblich. Beispiele: sehr hohe `max.text.characters`-Werte, ungewöhnliche
|
||||
Timeouts.
|
||||
- **Hinweise:** Informationen ohne Handlungsbedarf.
|
||||
|
||||
Warnungen und Hinweise verhindern das Speichern nicht. Vor dem Speichern eines
|
||||
als **nicht lauffähig** markierten Stands erscheint jedoch eine deutlich sichtbare
|
||||
Warnung im zentralen Meldungsbereich, die ausdrücklich auf mögliche Auswirkungen
|
||||
auf den nächsten headless Lauf hinweist. Speichern ist dennoch erlaubt.
|
||||
|
||||
Wirtschaftliche Warnschwellen für `max.text.characters`:
|
||||
|
||||
| Wertebereich | Bewertung |
|
||||
|---|---|
|
||||
| bis 1.000 | unkritisch |
|
||||
| 1.001 – 3.000 | Warnung |
|
||||
| ab 3.001 | starke Warnung |
|
||||
|
||||
`max.pages` wird als Plausibilitäts- und Performance-Hinweis behandelt.
|
||||
|
||||
---
|
||||
|
||||
## 7. Provider-Bedienung und Modellabruf
|
||||
|
||||
### 7.1 Provider-ComboBox
|
||||
|
||||
Im Bereich „Provider" befindet sich eine ComboBox zur Auswahl des aktiven
|
||||
Providers. In V2.0 stehen zwei Einträge zur Verfügung:
|
||||
|
||||
- **Claude**
|
||||
- **OpenAI-kompatibel**
|
||||
|
||||
Nur der aktuell ausgewählte Provider-Bereich ist im Formular sichtbar. Der
|
||||
verdeckte Provider-Block behält seine Daten; ein Providerwechsel löscht die
|
||||
Konfiguration des anderen Blocks nicht.
|
||||
|
||||
### 7.2 Automatischer Modellabruf
|
||||
|
||||
Bei jedem Providerwechsel startet der Modellabruf automatisch auf einem
|
||||
Hintergrund-Worker-Thread. Wird die Modellliste erfolgreich geladen, erscheint
|
||||
eine nicht editierbare ComboBox mit den verfügbaren Modellen. Das erste Modell
|
||||
der Liste ist automatisch vorbelegt.
|
||||
|
||||
Kann keine Modellliste abgerufen werden (z. B. wegen fehlendem oder falschem
|
||||
API-Key, unerreichbarem Endpoint), erscheint statt der ComboBox ein leeres
|
||||
Texteingabefeld. In diesem Fall muss der Modellname manuell eingetragen werden.
|
||||
|
||||
Wurde zuvor ein Modellname manuell eingetragen und wird später eine echte
|
||||
Modellliste geladen, in der dieser Wert nicht vorkommt, wird der manuell
|
||||
eingetragene Modellname verworfen. Es wird dann das erste Modell der Liste
|
||||
vorbelegt.
|
||||
|
||||
---
|
||||
|
||||
## 8. Dirty-State und Schutzdialoge
|
||||
|
||||
Sobald eine geladene oder neu erzeugte Konfiguration bearbeitet wird, gilt der
|
||||
Editor als „dirty" (ungespeicherte Änderungen). Zwei visuelle Markierungen
|
||||
zeigen diesen Zustand an:
|
||||
|
||||
- Ein **`*`**-Präfix im Fenstertitel
|
||||
- Ein kleines **„geändert"**-Label im Header
|
||||
|
||||
Vor den Aktionen „Neu", „Öffnen" und beim Schließen des Fensters prüft die GUI,
|
||||
ob ungespeicherte Änderungen vorhanden sind. Ist dies der Fall, erscheint ein
|
||||
Schutzdialog mit drei Optionen:
|
||||
|
||||
| Option | Wirkung |
|
||||
|--------|---------|
|
||||
| **Speichern** | Speichert die Änderungen und führt die Aktion danach aus |
|
||||
| **Verwerfen** | Verwirft die Änderungen und führt die Aktion aus |
|
||||
| **Abbrechen** | Bricht die Aktion ab; die Änderungen bleiben erhalten |
|
||||
|
||||
---
|
||||
|
||||
## 9. `.bak`-Sicherung beim Überschreiben und Legacy-Migration
|
||||
|
||||
### 9.1 Sicherung beim Überschreiben
|
||||
|
||||
Bevor eine bestehende `.properties`-Datei überschrieben wird, legt die GUI
|
||||
automatisch eine Sicherungskopie an:
|
||||
|
||||
- Standardfall: `<dateiname>.bak` (im selben Verzeichnis)
|
||||
- Falls `.bak` bereits existiert: `<dateiname>.bak.1`, `.bak.2`, …
|
||||
- Bestehende Sicherungen werden **niemals überschrieben**.
|
||||
|
||||
Dieses Schema gilt sowohl für „Speichern unter" bei existierendem Ziel als auch
|
||||
für die Legacy-Migration beim Öffnen.
|
||||
|
||||
### 9.2 Legacy-Migration
|
||||
|
||||
Ältere `.properties`-Dateien, die noch die flachen Schlüssel `api.baseUrl`,
|
||||
`api.model`, `api.timeoutSeconds` und `api.key` verwenden, erkennt die GUI beim
|
||||
Öffnen automatisch als Legacy-Format. Sie führt folgende Schritte durch:
|
||||
|
||||
1. `.bak`-Sicherung der Originaldatei anlegen (nach dem Schema aus 9.1)
|
||||
2. Inhalt ins aktuelle Mehrprovider-Schema überführen:
|
||||
- Legacy-Werte werden dem Namensraum `openai-compatible` zugeordnet
|
||||
- `ai.provider.active=openai-compatible` wird ergänzt
|
||||
3. Die migrierte Konfiguration wird im Editor angezeigt
|
||||
|
||||
Die durchgeführte Migration wird im zentralen Meldungsbereich sichtbar gemeldet,
|
||||
damit der Benutzer die Änderung nachvollziehen kann.
|
||||
|
||||
---
|
||||
|
||||
## 10. API-Key-Auflösungsreihenfolge
|
||||
|
||||
Der API-Key eines Providers wird in folgender Priorität aufgelöst:
|
||||
|
||||
1. **Providerspezifische Umgebungsvariable** (höchste Priorität)
|
||||
2. Für **OpenAI-kompatibel** zusätzlich: Legacy-Umgebungsvariable
|
||||
`PDF_UMBENENNER_API_KEY` (aus dem früheren Einzelprovider-Stand)
|
||||
3. **Property-Wert** in der `.properties`-Datei
|
||||
|
||||
| Provider | Providerspezifische Umgebungsvariable |
|
||||
|---|---|
|
||||
| Claude | `ANTHROPIC_API_KEY` |
|
||||
| OpenAI-kompatibel | `OPENAI_COMPATIBLE_API_KEY` |
|
||||
|
||||
Für **OpenAI-kompatibel** wird zusätzlich die Legacy-Variable
|
||||
`PDF_UMBENENNER_API_KEY` akzeptiert, falls die providerspezifische Variable
|
||||
nicht gesetzt ist. Für Claude gibt es keine Legacy-Variable.
|
||||
|
||||
Die GUI zeigt unterhalb des API-Key-Felds die Herkunft des aktuell wirksamen
|
||||
Schlüssels an. Greift eine Umgebungsvariable, erscheint ein Hinweis mit dem
|
||||
Namen der Variablen (z. B. „Aktuell wirksam aus Umgebungsvariable
|
||||
`ANTHROPIC_API_KEY`"). Ist kein Schlüssel aus keiner Quelle verfügbar, erscheint
|
||||
eine Warnung.
|
||||
|
||||
Das API-Key-Feld ist ein normales, unmaskiertes Textfeld. Ein leeres Feld entfernt
|
||||
den vorhandenen Property-Wert **nicht** stillschweigend, solange keine
|
||||
Umgebungsvariable greift. In diesem Fall bleibt der bestehende Property-Wert
|
||||
erhalten, und die GUI zeigt eine deutliche Warnung.
|
||||
|
||||
---
|
||||
|
||||
## 11. Pfadfelder und Datei-/Ordnerdialoge
|
||||
|
||||
Für die folgenden Konfigurationswerte stellt die GUI je ein Texteingabefeld
|
||||
sowie einen kleinen Button zum Öffnen des nativen Dialogs bereit:
|
||||
|
||||
| Feld | Dialog-Typ |
|
||||
|------|------------|
|
||||
| Quellordner | Ordnerdialog |
|
||||
| Zielordner | Ordnerdialog |
|
||||
| SQLite-Datei | Dateidialog |
|
||||
| Prompt-Datei | Dateidialog |
|
||||
|
||||
Pfade können auch direkt ins Texteingabefeld eingegeben werden, ohne den Dialog
|
||||
zu nutzen.
|
||||
|
||||
---
|
||||
|
||||
## 12. Windows-Hinweise zu gemappten Laufwerken
|
||||
|
||||
Die GUI sowie alle zugehörigen Pfadprüfungen akzeptieren Windows-Laufwerksbuchstaben
|
||||
wie `S:\`, `H:\` oder `C:\`. Gemappte Netzlaufwerke werden ausdrücklich unterstützt
|
||||
und werden nicht allein wegen eines dahinterliegenden UNC-Pfads abgelehnt.
|
||||
|
||||
UNC-Pfade (z. B. `\\server\freigabe\pfad\`) werden ebenfalls akzeptiert, sind aber
|
||||
nicht das primäre Format für den GUI-Betrieb.
|
||||
|
||||
Die GUI wird offiziell nur unter **Windows** unterstützt.
|
||||
|
||||
---
|
||||
|
||||
## 13. Bekannte Einschränkungen V2.0
|
||||
|
||||
| Einschränkung | Erläuterung |
|
||||
|---|---|
|
||||
| Kein manueller Verarbeitungslauf | Das Starten eines Batch-Laufs aus der GUI ist erst ab V2.1+ vorgesehen |
|
||||
| Kein Historien-Tab | Eine Ansicht der SQLite-Datenbank und Verarbeitungshistorie ist für spätere Ausbaustufen vorbehalten |
|
||||
| Kein Kosten-Tracking | Token-/Preisberechnungen sind für spätere Ausbaustufen vorbehalten |
|
||||
| Keine Erkennung externer Änderungen | Wird die `.properties`-Datei während einer GUI-Sitzung von außen geändert, erkennt die GUI dies nicht. Die GUI arbeitet weiterhin auf dem zuletzt geladenen Stand |
|
||||
| Keine Koordination mit parallelen headless Läufen | Läuft gleichzeitig ein headless Batch-Lauf, koordinieren sich GUI und headless Betrieb nicht. Schreibkonflikte können entstehen, wenn dieselbe `.properties`-Datei gleichzeitig über die GUI gespeichert und vom headless Lauf gelesen wird |
|
||||
| GUI nur für Windows | Die GUI wird offiziell nur unter Windows unterstützt; der headless Betrieb ist für Windows Server geeignet |
|
||||
+7
@@ -108,6 +108,13 @@ public final class GuiConfigurationEditorWorkspace {
|
||||
private final Label configurationPathValueLabel = new Label();
|
||||
/** Package-private to allow visibility assertions in smoke tests. */
|
||||
final Label dirtyMarkerLabel = new Label("geändert");
|
||||
/**
|
||||
* Package-private to allow startup-notice visibility assertions in smoke tests.
|
||||
* Returns the status label used to display startup notices and status messages in the header.
|
||||
*/
|
||||
Label statusNoticeLabel() {
|
||||
return statusLabel;
|
||||
}
|
||||
private final Label welcomeTitleLabel = new Label("Willkommen");
|
||||
private final Label welcomeTextLabel = new Label(WELCOME_TEXT);
|
||||
/** Package-private to allow node lookups in smoke tests. */
|
||||
|
||||
+90
@@ -2,6 +2,7 @@ package de.gecheckt.pdf.umbenenner.adapter.in.gui;
|
||||
|
||||
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.io.IOException;
|
||||
@@ -323,6 +324,95 @@ class GuiEditorIntegrationTest {
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// GUI startup with a non-existent --config path: startup notice rendered in header
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Verifies that when the workspace is constructed with a startup notice (as Bootstrap does
|
||||
* when {@code --config} points to a non-existent file), the notice text is rendered in the
|
||||
* visible header status label.
|
||||
* <p>
|
||||
* This complements {@link #guiStartup_withNonExistentConfigPath_usesBlankStateAndCarriesStartupNotice}
|
||||
* which only verifies the blank editor state. This test verifies the user-visible side: the
|
||||
* notice must be rendered so the user can read it.
|
||||
*
|
||||
* @throws Exception if the FX thread task fails or times out
|
||||
*/
|
||||
@Test
|
||||
void guiStartup_withNonExistentConfigPath_noticeIsRenderedInHeaderStatusLabel()
|
||||
throws Exception {
|
||||
String notice = "Konfigurationsdatei nicht gefunden: /no/such/file.properties\n"
|
||||
+ "Die GUI startet ohne Konfigurationsdatei.";
|
||||
GuiConfigurationEditorState blankState = GuiConfigurationEditorStateFactory.createBlankStartState();
|
||||
GuiConfigurationFileWriter noOpWriter = (values, path) -> GuiConfigurationSaveResult.saved(path);
|
||||
GuiStartupContext context = new GuiStartupContext(
|
||||
blankState,
|
||||
Optional.of(notice),
|
||||
configFilePath -> GuiConfigurationEditorStateFactory.createBlankStartState(),
|
||||
noOpWriter,
|
||||
req -> new de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.ModelCatalogResult.IncompleteConfiguration(req.providerIdentifier(), "kein Port im Test"),
|
||||
(family, propertyValue) -> de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor.absent(),
|
||||
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderTechnicalTestService(
|
||||
req2 -> new de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.ModelCatalogResult.IncompleteConfiguration(req2.providerIdentifier(), "kein Port im Test"),
|
||||
(fam2, pv2) -> de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor.absent()),
|
||||
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.PathCheckPort() {
|
||||
@Override public boolean isDirectoryReadable(String p) { return false; }
|
||||
@Override public boolean isDirectoryWritableOrCreatable(String p) { return false; }
|
||||
@Override public boolean isFileReadable(String p) { return false; }
|
||||
@Override public boolean isSqlitePathUsable(String p) { return false; }
|
||||
},
|
||||
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.TechnicalTestOrchestrator(
|
||||
new de.gecheckt.pdf.umbenenner.application.validation.editor.EditorConfigurationValidator(),
|
||||
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.PathCheckPort() {
|
||||
@Override public boolean isDirectoryReadable(String p) { return false; }
|
||||
@Override public boolean isDirectoryWritableOrCreatable(String p) { return false; }
|
||||
@Override public boolean isFileReadable(String p) { return false; }
|
||||
@Override public boolean isSqlitePathUsable(String p) { return false; }
|
||||
},
|
||||
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderTechnicalTestService(
|
||||
req99 -> new de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.ModelCatalogResult.IncompleteConfiguration(req99.providerIdentifier(), "kein Port im Test"),
|
||||
(fam99, pv99) -> de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor.absent())),
|
||||
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService(
|
||||
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ResourceCreationPort() {
|
||||
@Override public de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionOutcome createDirectory(de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionSuggestion.CreateDirectory s) { return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionOutcome.NotAttempted(s, "kein Port im Test"); }
|
||||
@Override public de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionOutcome createPromptFile(de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionSuggestion.CreatePromptFile s) { return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionOutcome.NotAttempted(s, "kein Port im Test"); }
|
||||
@Override public de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionOutcome prepareSqlitePath(de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionSuggestion.PrepareSqlitePath s) { return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionOutcome.NotAttempted(s, "kein Port im Test"); }
|
||||
}));
|
||||
|
||||
AtomicReference<Throwable> error = new AtomicReference<>();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
GuiConfigurationEditorWorkspace workspace = new GuiConfigurationEditorWorkspace(context);
|
||||
|
||||
// The startup notice must be rendered in the visible header status label.
|
||||
javafx.scene.control.Label noticeLabel = workspace.statusNoticeLabel();
|
||||
assertNotNull(noticeLabel, "Header status label must not be null");
|
||||
assertTrue(noticeLabel.isVisible(),
|
||||
"Header status label must be visible when a startup notice is present");
|
||||
assertTrue(noticeLabel.isManaged(),
|
||||
"Header status label must be managed when a startup notice is present");
|
||||
assertFalse(noticeLabel.getText().isBlank(),
|
||||
"Header status label text must not be blank when startup notice is present");
|
||||
assertTrue(noticeLabel.getText().contains("Konfigurationsdatei nicht gefunden"),
|
||||
"Header status label must contain the notice text; got: " + noticeLabel.getText());
|
||||
|
||||
} catch (Throwable t) {
|
||||
error.set(t);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
assertTrue(latch.await(FX_TIMEOUT_SECONDS, TimeUnit.SECONDS),
|
||||
"FX task must complete within timeout");
|
||||
if (error.get() != null) {
|
||||
throw new AssertionError("FX thread threw an exception", error.get());
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// --config path resolution: static helper (no FX thread needed)
|
||||
// =========================================================================
|
||||
|
||||
+287
@@ -1,6 +1,7 @@
|
||||
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 static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@@ -19,6 +20,14 @@ import org.junit.jupiter.api.io.TempDir;
|
||||
* <p>
|
||||
* 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.
|
||||
* Regression scenarios included:
|
||||
* <ul>
|
||||
* <li>Headless start without {@code --config}: uses default path, exits 0</li>
|
||||
* <li>Headless start with valid {@code --config}: uses supplied path, exits 0</li>
|
||||
* <li>Headless start with non-existent {@code --config}: hard startup failure, exits 1</li>
|
||||
* <li>Invalid configuration: controlled failure, exits 1</li>
|
||||
* <li>Headless output free of JavaFX initialisation markers</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Tests are executed by the maven-failsafe-plugin after the package phase.
|
||||
* The *IT suffix ensures failsafe picks them up as integration tests.
|
||||
@@ -277,4 +286,282 @@ class ExecutableJarSmokeTestIT {
|
||||
"Output should indicate configuration/validation error. Got: " + outputText
|
||||
);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Regression: headless start with explicit valid --config path
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Regression test: {@code --headless --config <valid-path>} must locate the supplied
|
||||
* configuration file and complete successfully with exit code 0.
|
||||
* <p>
|
||||
* This guards against a regression where the explicit config path would be silently ignored
|
||||
* and the default path would be used instead (or vice-versa causing a startup failure).
|
||||
*/
|
||||
@Test
|
||||
void jar_headlessWithExplicitValidConfigPath_exitCode0(@TempDir Path workDir) throws Exception {
|
||||
Path configDir = Files.createDirectory(workDir.resolve("mycfg"));
|
||||
Path sourceDir = Files.createDirectory(workDir.resolve("source"));
|
||||
Path targetDir = Files.createDirectory(workDir.resolve("target"));
|
||||
Path logsDir = Files.createDirectory(workDir.resolve("logs"));
|
||||
Path dbParent = Files.createDirectory(workDir.resolve("data"));
|
||||
Path promptDir = Files.createDirectory(workDir.resolve("mycfg/prompts"));
|
||||
|
||||
Path sqliteFile = Files.createFile(dbParent.resolve("pdf-umbenenner.db"));
|
||||
Path promptTemplateFile = Files.createFile(promptDir.resolve("template.txt"));
|
||||
Files.writeString(promptTemplateFile, "Test prompt template for smoke test.");
|
||||
|
||||
// Store the config in a non-default location (not config/application.properties)
|
||||
Path configFile = configDir.resolve("custom.properties");
|
||||
String validConfig = """
|
||||
source.folder=%s
|
||||
target.folder=%s
|
||||
sqlite.file=%s
|
||||
ai.provider.active=openai-compatible
|
||||
ai.provider.openai-compatible.baseUrl=http://localhost:8080/api
|
||||
ai.provider.openai-compatible.model=gpt-4o-mini
|
||||
ai.provider.openai-compatible.timeoutSeconds=30
|
||||
ai.provider.openai-compatible.apiKey=test-api-key-for-smoke-test
|
||||
max.retries.transient=3
|
||||
max.pages=10
|
||||
max.text.characters=5000
|
||||
prompt.template.file=%s
|
||||
runtime.lock.file=%s/lock.pid
|
||||
log.directory=%s
|
||||
log.level=INFO
|
||||
""".formatted(
|
||||
sourceDir.toAbsolutePath(),
|
||||
targetDir.toAbsolutePath(),
|
||||
sqliteFile.toAbsolutePath(),
|
||||
promptTemplateFile.toAbsolutePath(),
|
||||
workDir.toAbsolutePath(),
|
||||
logsDir.toAbsolutePath()
|
||||
);
|
||||
Files.writeString(configFile, validConfig);
|
||||
|
||||
Path shadedJar = findShadedJar();
|
||||
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(JAVA_EXECUTABLE);
|
||||
command.add("-jar");
|
||||
command.add(shadedJar.toString());
|
||||
command.add("--headless");
|
||||
command.add("--config");
|
||||
command.add(configFile.toAbsolutePath().toString());
|
||||
|
||||
// Run in a fresh empty directory — no default config/application.properties present,
|
||||
// so the process must load via the explicit --config path.
|
||||
Path emptyWorkDir = Files.createDirectory(workDir.resolve("empty-cwd"));
|
||||
ProcessBuilder pb = new ProcessBuilder(command);
|
||||
pb.directory(emptyWorkDir.toFile());
|
||||
pb.redirectErrorStream(true);
|
||||
|
||||
System.out.println("[SMOKE-TEST-EXPLICIT-CONFIG] Command: " + String.join(" ", command));
|
||||
|
||||
Process process = pb.start();
|
||||
boolean completed = process.waitFor(PROCESS_TIMEOUT_MS, java.util.concurrent.TimeUnit.MILLISECONDS);
|
||||
byte[] outputBytes = process.getInputStream().readAllBytes();
|
||||
String outputText = new String(outputBytes);
|
||||
|
||||
System.out.println("[SMOKE-TEST-EXPLICIT-CONFIG] Exit code: " + process.exitValue());
|
||||
System.out.println("[SMOKE-TEST-EXPLICIT-CONFIG] Output:\n" + outputText);
|
||||
|
||||
assertTrue(completed, "Process should complete within timeout");
|
||||
assertEquals(0, process.exitValue(),
|
||||
"Headless start with explicit valid --config path must exit 0. Output: " + outputText);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Regression: headless start with non-existent --config path → hard failure
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Regression test: {@code --headless --config <non-existent-path>} must be treated as
|
||||
* a hard startup error and produce exit code 1.
|
||||
* <p>
|
||||
* According to the startup semantics, a missing {@code --config} target in headless mode
|
||||
* is never a silent fallback but always a configuration error that blocks the run.
|
||||
* The output must contain a keyword that helps the operator diagnose the root cause.
|
||||
*/
|
||||
@Test
|
||||
void jar_headlessWithNonExistentConfigPath_exitCode1(@TempDir Path workDir) throws Exception {
|
||||
Path shadedJar = findShadedJar();
|
||||
|
||||
String missingPath = workDir.resolve("does-not-exist.properties").toAbsolutePath().toString();
|
||||
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(JAVA_EXECUTABLE);
|
||||
command.add("-jar");
|
||||
command.add(shadedJar.toString());
|
||||
command.add("--headless");
|
||||
command.add("--config");
|
||||
command.add(missingPath);
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(command);
|
||||
pb.directory(workDir.toFile());
|
||||
pb.redirectErrorStream(true);
|
||||
|
||||
System.out.println("[SMOKE-TEST-MISSING-CONFIG] Command: " + String.join(" ", command));
|
||||
|
||||
Process process = pb.start();
|
||||
boolean completed = process.waitFor(PROCESS_TIMEOUT_MS, java.util.concurrent.TimeUnit.MILLISECONDS);
|
||||
byte[] outputBytes = process.getInputStream().readAllBytes();
|
||||
String outputText = new String(outputBytes);
|
||||
|
||||
System.out.println("[SMOKE-TEST-MISSING-CONFIG] Exit code: " + process.exitValue());
|
||||
System.out.println("[SMOKE-TEST-MISSING-CONFIG] Output:\n" + outputText);
|
||||
|
||||
assertTrue(completed, "Process should complete within timeout");
|
||||
assertEquals(1, process.exitValue(),
|
||||
"Headless start with non-existent --config path must exit 1. Output: " + outputText);
|
||||
|
||||
// Verify that the output contains a diagnostic keyword so operators can trace the cause.
|
||||
// Only stable keywords are checked; exact message text may evolve.
|
||||
assertTrue(
|
||||
outputText.toLowerCase().contains("not found")
|
||||
|| outputText.toLowerCase().contains("does not exist")
|
||||
|| outputText.toLowerCase().contains("missing")
|
||||
|| outputText.toLowerCase().contains("error")
|
||||
|| outputText.toLowerCase().contains("config"),
|
||||
"Output must contain a diagnostic keyword for the missing config file. Got: " + outputText
|
||||
);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Regression: headless output must be free of JavaFX initialisation markers
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Regression test verifying that a headless start does not trigger JavaFX initialisation.
|
||||
* <p>
|
||||
* The headless path must not require a JavaFX runtime. This test runs the JAR in headless
|
||||
* mode and checks that no JavaFX-specific strings appear in the combined stdout/stderr.
|
||||
* Strings checked: "JavaFX", "Platform.startup", "Monocle", "javafx".
|
||||
* <p>
|
||||
* This is a process-level complement to the unit-level proof in
|
||||
* {@link BootstrapRunnerStartupDispatchTest#headlessStart_doesNotInvokeGuiAdapterFactory()}.
|
||||
* That unit test proves the GuiAdapterFactory is never called; this test proves the
|
||||
* observable JAR output is likewise free of JavaFX initialisation noise.
|
||||
*/
|
||||
@Test
|
||||
void jar_headlessStart_outputFreeOfJavaFxInitialisationMarkers(@TempDir Path workDir) throws Exception {
|
||||
Path configDir = Files.createDirectory(workDir.resolve("config"));
|
||||
Path sourceDir = Files.createDirectory(workDir.resolve("source"));
|
||||
Path targetDir = Files.createDirectory(workDir.resolve("target"));
|
||||
Path logsDir = Files.createDirectory(workDir.resolve("logs"));
|
||||
Path dbParent = Files.createDirectory(workDir.resolve("data"));
|
||||
Path promptDir = Files.createDirectory(workDir.resolve("config/prompts"));
|
||||
|
||||
Path sqliteFile = Files.createFile(dbParent.resolve("pdf-umbenenner.db"));
|
||||
Path promptTemplateFile = Files.createFile(promptDir.resolve("template.txt"));
|
||||
Files.writeString(promptTemplateFile, "Test prompt template for headless JavaFX-freedom check.");
|
||||
|
||||
Path configFile = configDir.resolve("application.properties");
|
||||
String validConfig = """
|
||||
source.folder=%s
|
||||
target.folder=%s
|
||||
sqlite.file=%s
|
||||
ai.provider.active=openai-compatible
|
||||
ai.provider.openai-compatible.baseUrl=http://localhost:8080/api
|
||||
ai.provider.openai-compatible.model=gpt-4o-mini
|
||||
ai.provider.openai-compatible.timeoutSeconds=30
|
||||
ai.provider.openai-compatible.apiKey=test-api-key-javafx-check
|
||||
max.retries.transient=3
|
||||
max.pages=10
|
||||
max.text.characters=5000
|
||||
prompt.template.file=%s
|
||||
runtime.lock.file=%s/lock.pid
|
||||
log.directory=%s
|
||||
log.level=INFO
|
||||
""".formatted(
|
||||
sourceDir.toAbsolutePath(),
|
||||
targetDir.toAbsolutePath(),
|
||||
sqliteFile.toAbsolutePath(),
|
||||
promptTemplateFile.toAbsolutePath(),
|
||||
workDir.toAbsolutePath(),
|
||||
logsDir.toAbsolutePath()
|
||||
);
|
||||
Files.writeString(configFile, validConfig);
|
||||
|
||||
Path shadedJar = findShadedJar();
|
||||
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(JAVA_EXECUTABLE);
|
||||
command.add("-jar");
|
||||
command.add(shadedJar.toString());
|
||||
command.add("--headless");
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(command);
|
||||
pb.directory(workDir.toFile());
|
||||
pb.redirectErrorStream(true);
|
||||
|
||||
System.out.println("[SMOKE-TEST-JAVAFX-FREEDOM] Command: " + String.join(" ", command));
|
||||
|
||||
Process process = pb.start();
|
||||
boolean completed = process.waitFor(PROCESS_TIMEOUT_MS, java.util.concurrent.TimeUnit.MILLISECONDS);
|
||||
byte[] outputBytes = process.getInputStream().readAllBytes();
|
||||
String outputText = new String(outputBytes);
|
||||
|
||||
System.out.println("[SMOKE-TEST-JAVAFX-FREEDOM] Exit code: " + process.exitValue());
|
||||
System.out.println("[SMOKE-TEST-JAVAFX-FREEDOM] Output:\n" + outputText);
|
||||
|
||||
assertTrue(completed, "Process should complete within timeout");
|
||||
assertEquals(0, process.exitValue(),
|
||||
"Headless start must exit 0 for the JavaFX-freedom check to be meaningful. "
|
||||
+ "Output: " + outputText);
|
||||
|
||||
// JavaFX initialisation would produce one of these markers in stdout/stderr.
|
||||
// Their absence is the evidence that the headless path is JavaFX-free at runtime.
|
||||
assertFalse(
|
||||
outputText.contains("Platform.startup")
|
||||
|| outputText.contains("Monocle")
|
||||
|| outputText.contains("com.sun.javafx")
|
||||
|| outputText.contains("javafx.application"),
|
||||
"Headless output must not contain JavaFX initialisation markers. Got:\n" + outputText
|
||||
);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Shared helper: locate the shaded JAR
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Locates the shaded executable JAR produced by the {@code maven-shade-plugin}.
|
||||
* <p>
|
||||
* Searches in {@code pdf-umbenenner-bootstrap/target} relative to the Maven reactor root
|
||||
* ({@code user.dir}) first, then falls back to the local {@code target/} directory.
|
||||
*
|
||||
* @return absolute path to the shaded JAR
|
||||
* @throws AssertionError if no matching JAR can be found
|
||||
*/
|
||||
private Path findShadedJar() {
|
||||
Path projectRoot = Paths.get(System.getProperty("user.dir"));
|
||||
Path bootstrapTarget = projectRoot.resolve("pdf-umbenenner-bootstrap/target");
|
||||
if (!Files.exists(bootstrapTarget)) {
|
||||
bootstrapTarget = Paths.get("target");
|
||||
}
|
||||
|
||||
assertTrue(Files.exists(bootstrapTarget),
|
||||
"Bootstrap target directory must exist: " + bootstrapTarget);
|
||||
|
||||
File[] jars = bootstrapTarget.toFile().listFiles(
|
||||
(dir, name) -> name.endsWith(".jar")
|
||||
&& !name.contains("original")
|
||||
&& !name.contains("tests"));
|
||||
|
||||
assertNotNull(jars, "JAR files should exist in target directory");
|
||||
assertTrue(jars.length > 0, "At least one JAR should exist in target directory");
|
||||
|
||||
Path shadedJar = Paths.get(jars[0].getAbsolutePath());
|
||||
for (File jar : jars) {
|
||||
if (jar.getName().contains("shaded")
|
||||
|| jar.getName().equals("pdf-umbenenner-bootstrap-0.0.1-SNAPSHOT.jar")) {
|
||||
shadedJar = jar.toPath().toAbsolutePath();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue(Files.exists(shadedJar), "Shaded JAR file must exist: " + shadedJar);
|
||||
return shadedJar;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user