#89 #90: Log-Verzeichnis-Prüfpunkt + betrieb.md MSI-Pfadwarnungen

#90: Neuer technischer Prüfpunkt LOG_DIRECTORY_USABLE (12. Checkpoint):
- Zeigt konfigurierten log.directory-Wert und aufgelösten absoluten Pfad
- Prüft ob Verzeichnis beschreibbar/anlegbar ist (WARNING, kein ERROR)
- Liest tatsächlichen Log-Datei-Pfad via Log4j2 LoggerContext → RollingFileAppender
- LogDiagnosticsPort als neuer Outbound-Port (application-Modul)
- Log4jLogDiagnosticsAdapter als Implementierung im bootstrap-Modul
- TechnicalTestRequest erhält logDirectory-Feld
- GuiTechnicalTestCoordinator erhält logDirectoryProvider-Supplier

#89: docs/betrieb.md – MSI-Betrieb um Pfadwarnungen erweitert:
- Warnung: relative Pfade lösen sich in schreibgeschütztes C:\Program Files\ auf
- Warnung: Backslashes in .properties werden als Java-Escape-Sequenzen interpretiert
- Betroffene Parameter mit Empfehlung zu absoluten Forward-Slash-Pfaden
- Beschreibung des neuen Log-Verzeichnis-Prüfpunkts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-04 17:02:51 +02:00
parent bd2be347f6
commit 479d176536
23 changed files with 376 additions and 88 deletions
+44 -6
View File
@@ -550,13 +550,51 @@ Installationsverzeichnis ab. **Der Betreiber muss diese Beispieldatei manuell na
Windows-SmartScreen-Warnung, die durch „Weitere Informationen → Trotzdem ausführen" Windows-SmartScreen-Warnung, die durch „Weitere Informationen → Trotzdem ausführen"
bestätigt werden muss. Code-Signing ist für spätere Ausbaustufen vorgesehen. bestätigt werden muss. Code-Signing ist für spätere Ausbaustufen vorgesehen.
**Empfehlung für Prompt- und Konfigurationspfade im MSI-Betrieb:** **Empfehlung für Pfade im MSI-Betrieb:**
Für den MSI-Betrieb (Startmenü, Task Scheduler) wird **empfohlen**, Für den MSI-Betrieb (Startmenü, Task Scheduler) müssen alle Dateipfade als **absolute Pfade**
`prompt.template.file` und `sqlite.file` als **absolute Pfade** zu konfigurieren konfiguriert werden. Relative Pfade werden relativ zum Installationsverzeichnis
oder auf `%APPDATA%`- bzw. `%ProgramData%`-Verzeichnisse zu zeigen. `C:\Program Files\PDF KI Renamer\` aufgelöst, das **schreibgeschützt** ist. Dadurch
Relative Pfade beziehen sich auf das Arbeitsverzeichnis, das je nach Startart variiert schlagen Schreibversuche (Logs, SQLite-Datenbank, Lock-Datei) ohne Fehlermeldung fehl.
(siehe Abschnitt „Prompt-Konfiguration").
> **Warnung Relative Pfade im MSI-Betrieb nicht verwenden:**
> Pfade wie `./logs`, `./work/local/logs` oder `logs/` werden im MSI-Betrieb relativ
> zum Installationsverzeichnis aufgelöst. Das Installationsverzeichnis ist für normale
> Benutzerkonten schreibgeschützt. Log4j2 scheitert dann still, ohne eine sichtbare
> Fehlermeldung zu erzeugen.
> **Warnung Backslashes in `.properties`-Dateien:**
> In Java-`.properties`-Dateien werden Backslashes (`\`) als Escape-Zeichen interpretiert.
> Windows-Pfade wie `C:\Users\Funny\Logs` müssen entweder mit Forward-Slashes
> (`C:/Users/Funny/Logs`) oder mit doppelten Backslashes (`C:\\Users\\Funny\\Logs`)
> angegeben werden. Einfache Backslashes werden stillschweigend falsch interpretiert.
Betroffene Parameter:
| Parameter | Empfehlung |
|---|---|
| `log.directory` | Absoluter Pfad, z. B. `C:/ProgramData/PDF KI Renamer/logs` |
| `runtime.lock.file` | Absoluter Pfad, z. B. `C:/ProgramData/PDF KI Renamer/pdf-umbenenner.lock` |
| `prompt.template.file` | Absoluter Pfad, z. B. `C:/ProgramData/PDF KI Renamer/config/prompts/template.txt` |
| `sqlite.file` | Absoluter Pfad, z. B. `C:/ProgramData/PDF KI Renamer/config/pdf-umbenenner.db` |
Das empfohlene Konfigurationsverzeichnis für alle schreibbaren Daten im MSI-Betrieb ist
`C:\ProgramData\PDF KI Renamer\`, da dieses Verzeichnis standardmäßig für alle
Benutzerkonten schreibbar ist und bei der Deinstallation erhalten bleibt.
**Diagnose: Log-Datei-Prüfpunkt in den technischen Tests**
Die technischen Tests (Schaltfläche „Technische Tests ausführen" im Konfigurationseditor)
enthalten einen dedizierten Prüfpunkt **„Log-Verzeichnis beschreibbar"**, der anzeigt:
- den konfigurierten `log.directory`-Wert (roh und als aufgelöster absoluter Pfad),
- ob das Verzeichnis vorhanden und beschreibbar ist,
- den tatsächlichen Log-Dateipfad aus der laufenden Log4j2-Konfiguration.
Ein nicht beschreibbares Log-Verzeichnis wird als **Warnung** angezeigt, nicht als Fehler
(die Anwendung kann ohne Datei-Logging laufen). Der Prüfpunkt hilft, den typischen
MSI-Betriebsfehler relatives `log.directory` auf schreibgeschütztem Installationspfad
frühzeitig zu erkennen.
### MSI-Release-Checkliste ### MSI-Release-Checkliste
@@ -495,6 +495,7 @@ public final class GuiConfigurationEditorWorkspace {
() -> editorState.loadedFileSnapshot() () -> editorState.loadedFileSnapshot()
.map(snapshot -> snapshot.filePath().toString()) .map(snapshot -> snapshot.filePath().toString())
.orElse(""), .orElse(""),
() -> editorState.values().logDirectory(),
pendingMessages, pendingMessages,
report -> { report -> {
technicalTestsButton.setDisable(false); technicalTestsButton.setDisable(false);
@@ -344,7 +344,8 @@ public record GuiStartupContext(
TechnicalTestOrchestrator noOpOrchestrator = new TechnicalTestOrchestrator( TechnicalTestOrchestrator noOpOrchestrator = new TechnicalTestOrchestrator(
new de.gecheckt.pdf.umbenenner.application.validation.editor.EditorConfigurationValidator(), new de.gecheckt.pdf.umbenenner.application.validation.editor.EditorConfigurationValidator(),
noOpPathCheckPort, noOpPathCheckPort,
noOpTestService); noOpTestService,
() -> java.util.Optional.empty());
ResourceCreationPort noOpResourceCreationPort = new ResourceCreationPort() { ResourceCreationPort noOpResourceCreationPort = new ResourceCreationPort() {
@Override @Override
public de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionOutcome public de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionOutcome
@@ -64,6 +64,7 @@ public final class GuiTechnicalTestCoordinator {
private final TechnicalTestOrchestrator orchestrator; private final TechnicalTestOrchestrator orchestrator;
private final Supplier<EditorValidationInput> inputProvider; private final Supplier<EditorValidationInput> inputProvider;
private final Supplier<String> configFilePathProvider; private final Supplier<String> configFilePathProvider;
private final Supplier<String> logDirectoryProvider;
private final List<GuiMessageEntry> pendingMessages; private final List<GuiMessageEntry> pendingMessages;
private final Consumer<TechnicalTestReport> postResultCallback; private final Consumer<TechnicalTestReport> postResultCallback;
@@ -89,6 +90,9 @@ public final class GuiTechnicalTestCoordinator {
* @param configFilePathProvider Lieferant des aktuell geladenen Konfigurationsdateipfads als String; * @param configFilePathProvider Lieferant des aktuell geladenen Konfigurationsdateipfads als String;
* gibt eine leere Zeichenkette zurück wenn keine Datei geladen ist; * gibt eine leere Zeichenkette zurück wenn keine Datei geladen ist;
* darf nicht {@code null} sein * darf nicht {@code null} sein
* @param logDirectoryProvider Lieferant des konfigurierten {@code log.directory}-Rohwerts;
* gibt eine leere Zeichenkette zurück wenn kein Wert konfiguriert ist;
* darf nicht {@code null} sein
* @param pendingMessages geteilte veränderliche Nachrichtenliste; darf nicht {@code null} sein * @param pendingMessages geteilte veränderliche Nachrichtenliste; darf nicht {@code null} sein
* @param postResultCallback Callback nach erfolgreicher Ergebnisanwendung; darf nicht {@code null} sein * @param postResultCallback Callback nach erfolgreicher Ergebnisanwendung; darf nicht {@code null} sein
* @throws NullPointerException wenn einer der Parameter {@code null} ist * @throws NullPointerException wenn einer der Parameter {@code null} ist
@@ -96,11 +100,13 @@ public final class GuiTechnicalTestCoordinator {
public GuiTechnicalTestCoordinator(TechnicalTestOrchestrator orchestrator, public GuiTechnicalTestCoordinator(TechnicalTestOrchestrator orchestrator,
Supplier<EditorValidationInput> inputProvider, Supplier<EditorValidationInput> inputProvider,
Supplier<String> configFilePathProvider, Supplier<String> configFilePathProvider,
Supplier<String> logDirectoryProvider,
List<GuiMessageEntry> pendingMessages, List<GuiMessageEntry> pendingMessages,
Consumer<TechnicalTestReport> postResultCallback) { Consumer<TechnicalTestReport> postResultCallback) {
this.orchestrator = Objects.requireNonNull(orchestrator, "orchestrator must not be null"); this.orchestrator = Objects.requireNonNull(orchestrator, "orchestrator must not be null");
this.inputProvider = Objects.requireNonNull(inputProvider, "inputProvider must not be null"); this.inputProvider = Objects.requireNonNull(inputProvider, "inputProvider must not be null");
this.configFilePathProvider = Objects.requireNonNull(configFilePathProvider, "configFilePathProvider must not be null"); this.configFilePathProvider = Objects.requireNonNull(configFilePathProvider, "configFilePathProvider must not be null");
this.logDirectoryProvider = Objects.requireNonNull(logDirectoryProvider, "logDirectoryProvider must not be null");
this.pendingMessages = Objects.requireNonNull(pendingMessages, "pendingMessages must not be null"); this.pendingMessages = Objects.requireNonNull(pendingMessages, "pendingMessages must not be null");
this.postResultCallback = Objects.requireNonNull(postResultCallback, "postResultCallback must not be null"); this.postResultCallback = Objects.requireNonNull(postResultCallback, "postResultCallback must not be null");
this.testThreadFactory = task -> { this.testThreadFactory = task -> {
@@ -134,7 +140,8 @@ public final class GuiTechnicalTestCoordinator {
pendingMessages.clear(); pendingMessages.clear();
EditorValidationInput input = inputProvider.get(); EditorValidationInput input = inputProvider.get();
String configFilePath = configFilePathProvider.get(); String configFilePath = configFilePathProvider.get();
TechnicalTestRequest request = new TechnicalTestRequest(input, configFilePath); String logDirectory = logDirectoryProvider.get();
TechnicalTestRequest request = new TechnicalTestRequest(input, configFilePath, logDirectory);
LOG.info("GUI-Gesamttest: Technische Tests ausführen gestartet."); LOG.info("GUI-Gesamttest: Technische Tests ausführen gestartet.");
@@ -234,6 +241,7 @@ public final class GuiTechnicalTestCoordinator {
case SOURCE_FOLDER_PRESENT -> "Quellordner vorhanden und lesbar"; case SOURCE_FOLDER_PRESENT -> "Quellordner vorhanden und lesbar";
case TARGET_FOLDER_USABLE -> "Zielordner vorhanden oder anlegbar sowie schreibbar"; case TARGET_FOLDER_USABLE -> "Zielordner vorhanden oder anlegbar sowie schreibbar";
case SQLITE_PATH_USABLE -> "SQLite-Pfad technisch nutzbar"; case SQLITE_PATH_USABLE -> "SQLite-Pfad technisch nutzbar";
case LOG_DIRECTORY_USABLE -> "Log-Verzeichnis beschreibbar";
}; };
} }
@@ -419,7 +419,8 @@ class GuiAdapterSmokeTest {
}, },
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderTechnicalTestService( 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"), 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())), (fam99, pv99) -> de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor.absent()),
() -> java.util.Optional.empty()),
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService( new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService(
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ResourceCreationPort() { 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 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"); }
@@ -345,7 +345,8 @@ class GuiEditorFieldBindingTest {
}, },
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderTechnicalTestService( 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"), 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())), (fam99, pv99) -> de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor.absent()),
() -> java.util.Optional.empty()),
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService( new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService(
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ResourceCreationPort() { 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 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"); }
@@ -137,7 +137,8 @@ class GuiEditorIntegrationTest {
}, },
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderTechnicalTestService( 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"), 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())), (fam99, pv99) -> de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor.absent()),
() -> java.util.Optional.empty()),
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService( new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService(
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ResourceCreationPort() { 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 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"); }
@@ -287,7 +288,8 @@ class GuiEditorIntegrationTest {
}, },
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderTechnicalTestService( 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"), 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())), (fam99, pv99) -> de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor.absent()),
() -> java.util.Optional.empty()),
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService( new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService(
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ResourceCreationPort() { 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 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"); }
@@ -371,7 +373,8 @@ class GuiEditorIntegrationTest {
}, },
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderTechnicalTestService( 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"), 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())), (fam99, pv99) -> de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor.absent()),
() -> java.util.Optional.empty()),
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService( new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService(
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ResourceCreationPort() { 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 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"); }
@@ -208,7 +208,8 @@ class GuiEditorRegressionSmokeTest {
}, },
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderTechnicalTestService( 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"), 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())), (fam99, pv99) -> de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor.absent()),
() -> java.util.Optional.empty()),
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService( new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService(
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ResourceCreationPort() { 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 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"); }
@@ -347,7 +348,8 @@ class GuiEditorRegressionSmokeTest {
}, },
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderTechnicalTestService( 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"), 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())), (fam99, pv99) -> de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor.absent()),
() -> java.util.Optional.empty()),
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService( new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService(
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ResourceCreationPort() { 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 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"); }
@@ -471,7 +473,8 @@ class GuiEditorRegressionSmokeTest {
}, },
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderTechnicalTestService( 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"), 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())), (fam99, pv99) -> de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor.absent()),
() -> java.util.Optional.empty()),
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService( new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService(
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ResourceCreationPort() { 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 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"); }
@@ -599,7 +602,8 @@ class GuiEditorRegressionSmokeTest {
}, },
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderTechnicalTestService( 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"), 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())), (fam99, pv99) -> de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor.absent()),
() -> java.util.Optional.empty()),
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService( new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService(
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ResourceCreationPort() { 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 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"); }
@@ -698,7 +702,8 @@ class GuiEditorRegressionSmokeTest {
}, },
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderTechnicalTestService( 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"), 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())), (fam99, pv99) -> de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor.absent()),
() -> java.util.Optional.empty()),
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService( new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService(
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ResourceCreationPort() { 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 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"); }
@@ -142,7 +142,8 @@ class GuiEditorValidationSmokeTest {
}, },
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderTechnicalTestService( 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"), 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())), (fam99, pv99) -> de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor.absent()),
() -> java.util.Optional.empty()),
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService( new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService(
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ResourceCreationPort() { 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 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"); }
@@ -272,7 +273,8 @@ class GuiEditorValidationSmokeTest {
}, },
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderTechnicalTestService( 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"), 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())), (fam99, pv99) -> de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor.absent()),
() -> java.util.Optional.empty()),
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService( new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService(
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ResourceCreationPort() { 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 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"); }
@@ -336,7 +336,8 @@ class GuiMessageAreaSmokeTest {
}, },
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderTechnicalTestService( 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"), 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())), (fam99, pv99) -> de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor.absent()),
() -> java.util.Optional.empty()),
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService( new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService(
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ResourceCreationPort() { 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 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"); }
@@ -478,7 +479,8 @@ class GuiMessageAreaSmokeTest {
}, },
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderTechnicalTestService( 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"), 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())), (fam99, pv99) -> de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor.absent()),
() -> java.util.Optional.empty()),
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService( new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService(
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ResourceCreationPort() { 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 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"); }
@@ -565,7 +567,8 @@ class GuiMessageAreaSmokeTest {
}, },
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderTechnicalTestService( 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"), 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())), (fam99, pv99) -> de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor.absent()),
() -> java.util.Optional.empty()),
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService( new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService(
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ResourceCreationPort() { 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 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"); }
@@ -888,7 +891,8 @@ class GuiMessageAreaSmokeTest {
}, },
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderTechnicalTestService( 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"), 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())), (fam99, pv99) -> de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor.absent()),
() -> java.util.Optional.empty()),
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService( new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService(
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ResourceCreationPort() { 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 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"); }
@@ -529,7 +529,8 @@ class GuiModelCatalogSmokeTest {
}, },
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderTechnicalTestService( 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"), 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())), (fam99, pv99) -> de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor.absent()),
() -> java.util.Optional.empty()),
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService( new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService(
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ResourceCreationPort() { 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 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"); }
@@ -39,7 +39,7 @@ import javafx.scene.control.Button;
* {@code technical-tests-button}.</li> * {@code technical-tests-button}.</li>
* <li>Triggering the coordinator synchronously populates {@code pendingMessages} * <li>Triggering the coordinator synchronously populates {@code pendingMessages}
* with entries tagged {@link GuiTechnicalTestCoordinator#SOURCE_TAG}.</li> * with entries tagged {@link GuiTechnicalTestCoordinator#SOURCE_TAG}.</li>
* <li>A second trigger appends a fresh batch of test entries (accumulation semantics).</li> * <li>A second trigger replaces the previous batch of test entries.</li>
* <li>The post-result callback is invoked after the result is applied.</li> * <li>The post-result callback is invoked after the result is applied.</li>
* </ul> * </ul>
* <p> * <p>
@@ -138,12 +138,12 @@ class GuiTechnicalTestCoordinatorSmokeTest {
/** /**
* Smoke test: after one trigger, the number of entries tagged SOURCE_TAG equals * Smoke test: after one trigger, the number of entries tagged SOURCE_TAG equals
* 11 (one per checkpoint) plus 1 summary entry = 12. * 12 (one per checkpoint) plus 1 summary entry = 13.
* *
* @throws Exception if the FX thread task fails or times out * @throws Exception if the FX thread task fails or times out
*/ */
@Test @Test
void trigger_producesElevenCheckpointEntriesPlusSummary() throws Exception { void trigger_producesTwelveCheckpointEntriesPlusSummary() throws Exception {
runOnFx(() -> { runOnFx(() -> {
List<GuiMessageEntry> messages = new ArrayList<>(); List<GuiMessageEntry> messages = new ArrayList<>();
GuiTechnicalTestCoordinator coordinator = buildSyncCoordinator(messages, report -> { }); GuiTechnicalTestCoordinator coordinator = buildSyncCoordinator(messages, report -> { });
@@ -155,9 +155,9 @@ class GuiTechnicalTestCoordinatorSmokeTest {
&& GuiTechnicalTestCoordinator.SOURCE_TAG.equals(m.source().get())) && GuiTechnicalTestCoordinator.SOURCE_TAG.equals(m.source().get()))
.count(); .count();
// 11 checkpoint entries + 1 summary entry = 12 // 12 checkpoint entries + 1 summary entry = 13
assertEquals(12, taggedCount, assertEquals(13, taggedCount,
"Expected 11 checkpoint entries + 1 summary entry = 12 tagged messages"); "Expected 12 checkpoint entries + 1 summary entry = 13 tagged messages");
}); });
} }
@@ -256,12 +256,14 @@ class GuiTechnicalTestCoordinatorSmokeTest {
TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator( TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator(
new EditorConfigurationValidator(), new EditorConfigurationValidator(),
noOpPathCheckPort(), noOpPathCheckPort(),
noOpProviderService()); noOpProviderService(),
() -> java.util.Optional.empty());
GuiTechnicalTestCoordinator coordinator = new GuiTechnicalTestCoordinator( GuiTechnicalTestCoordinator coordinator = new GuiTechnicalTestCoordinator(
orchestrator, orchestrator,
currentInput::get, // always reads the current reference currentInput::get, // always reads the current reference
() -> "", () -> "",
() -> "",
messages, messages,
report -> { }); report -> { });
@@ -365,7 +367,8 @@ class GuiTechnicalTestCoordinatorSmokeTest {
TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator( TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator(
new EditorConfigurationValidator(), new EditorConfigurationValidator(),
noOpPathCheckPort(), noOpPathCheckPort(),
noOpProviderService()); noOpProviderService(),
() -> java.util.Optional.empty());
EditorValidationInput blankInput = new EditorValidationInput( EditorValidationInput blankInput = new EditorValidationInput(
"claude", "claude",
@@ -380,6 +383,7 @@ class GuiTechnicalTestCoordinatorSmokeTest {
orchestrator, orchestrator,
() -> blankInput, () -> blankInput,
() -> "", () -> "",
() -> "",
messages, messages,
postResultCallback); postResultCallback);
@@ -806,7 +806,8 @@ class GuiUnsavedChangesGuardSmokeTest {
}, },
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderTechnicalTestService( 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"), 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())), (fam99, pv99) -> de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor.absent()),
() -> java.util.Optional.empty()),
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService( new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService(
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ResourceCreationPort() { 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 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"); }
@@ -851,7 +852,8 @@ class GuiUnsavedChangesGuardSmokeTest {
}, },
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderTechnicalTestService( 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"), 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())), (fam99, pv99) -> de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor.absent()),
() -> java.util.Optional.empty()),
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService( new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService(
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ResourceCreationPort() { 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 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"); }
@@ -323,7 +323,8 @@ class GuiValidateActionSmokeTest {
}, },
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderTechnicalTestService( 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"), req99 -> new de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.ModelCatalogResult.IncompleteConfiguration(req99.providerIdentifier(), "kein Port im Test"),
noOpApiKeyResolutionPort())), noOpApiKeyResolutionPort()),
() -> java.util.Optional.empty()),
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService( new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService(
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ResourceCreationPort() { 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 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"); }
@@ -390,7 +391,8 @@ class GuiValidateActionSmokeTest {
}, },
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderTechnicalTestService( 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"), req99 -> new de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.ModelCatalogResult.IncompleteConfiguration(req99.providerIdentifier(), "kein Port im Test"),
noOpApiKeyResolutionPort())), noOpApiKeyResolutionPort()),
() -> java.util.Optional.empty()),
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService( new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService(
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ResourceCreationPort() { 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 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"); }
@@ -84,5 +84,13 @@ public enum CheckpointId {
* zeigt auf eine vorhandene Datei oder auf einen beschreibbaren Ordner, in dem die * zeigt auf eine vorhandene Datei oder auf einen beschreibbaren Ordner, in dem die
* Datei neu angelegt werden kann. * Datei neu angelegt werden kann.
*/ */
SQLITE_PATH_USABLE SQLITE_PATH_USABLE,
/**
* Log-Verzeichnis beschreibbar das konfigurierte (oder Standard-)Log-Verzeichnis
* ist vorhanden und schreibbar. Zeigt zusätzlich den tatsächlichen Log-Dateipfad
* aus der aktiven Log4j2-Konfiguration an. Ein nicht beschreibbares Log-Verzeichnis
* ist eine Warnung, kein harter Fehler.
*/
LOG_DIRECTORY_USABLE
} }
@@ -0,0 +1,28 @@
package de.gecheckt.pdf.umbenenner.application.validation.technicaltest;
import java.util.Optional;
/**
* Ausgehender Port zur Diagnose des aktiven Log-Ausgabepfads.
* <p>
* Implementierungen lesen den tatsächlich von der Logging-Infrastruktur verwendeten
* Dateipfad aus der laufenden Konfiguration des Logging-Frameworks aus. Der Port ist
* provider-neutral; er kennt weder Log4j2 noch andere Framework-spezifische Typen.
* <p>
* Diese Information ergänzt den konfigurierten {@code log.directory}-Wert aus der
* Properties-Datei und zeigt, wo Logeinträge tatsächlich landen unabhängig davon,
* ob das Log-Verzeichnis zum Zeitpunkt des Tests beschreibbar ist.
*/
public interface LogDiagnosticsPort {
/**
* Ermittelt den absoluten Dateipfad der aktiven Log-Ausgabedatei.
* <p>
* Gibt einen leeren {@link Optional} zurück, wenn der Pfad nicht bestimmbar ist
* beispielsweise weil kein dateibasierter Appender aktiv ist oder die
* Logging-Konfiguration nicht ausgelesen werden kann.
*
* @return absoluter Pfad der aktiven Log-Datei; leer wenn nicht bestimmbar
*/
Optional<String> resolveActiveLogFilePath();
}
@@ -17,16 +17,18 @@ import de.gecheckt.pdf.umbenenner.application.validation.editor.EditorValidation
/** /**
* Orchestrator für den vollständigen technischen Gesamttest der GUI-Konfiguration. * Orchestrator für den vollständigen technischen Gesamttest der GUI-Konfiguration.
* <p> * <p>
* Führt alle elf definierten Prüfpunkte in drei voneinander unabhängigen Blöcken aus: * Führt alle zwölf definierten Prüfpunkte in drei voneinander unabhängigen Blöcken aus:
* <ol> * <ol>
* <li><strong>Lokale Validierung:</strong> Prüft den Editorzustand ohne I/O mithilfe des * <li><strong>Lokale Validierung:</strong> Prüft den Editorzustand ohne I/O mithilfe des
* {@link EditorConfigurationValidator}. Erzeugt Ergebnisse für * {@link EditorConfigurationValidator}. Erzeugt Ergebnisse für
* {@link CheckpointId#CONFIGURATION_BASIC_VALIDATION} und * {@link CheckpointId#CONFIGURATION_BASIC_VALIDATION} und
* {@link CheckpointId#PROVIDER_CONFIGURATION}.</li> * {@link CheckpointId#PROVIDER_CONFIGURATION}.</li>
* <li><strong>Pfadprüfungen:</strong> Prüft Quellordner, Zielordner, Prompt-Datei und * <li><strong>Pfadprüfungen:</strong> Prüft Quellordner, Zielordner, Prompt-Datei,
* SQLite-Pfad über den {@link PathCheckPort}. Erzeugt Ergebnisse für * SQLite-Pfad und Log-Verzeichnis über den {@link PathCheckPort} sowie den
* {@link LogDiagnosticsPort}. Erzeugt Ergebnisse für
* {@link CheckpointId#PROMPT_FILE_PRESENT}, {@link CheckpointId#SOURCE_FOLDER_PRESENT}, * {@link CheckpointId#PROMPT_FILE_PRESENT}, {@link CheckpointId#SOURCE_FOLDER_PRESENT},
* {@link CheckpointId#TARGET_FOLDER_USABLE} und {@link CheckpointId#SQLITE_PATH_USABLE}.</li> * {@link CheckpointId#TARGET_FOLDER_USABLE}, {@link CheckpointId#SQLITE_PATH_USABLE}
* und {@link CheckpointId#LOG_DIRECTORY_USABLE}.</li>
* <li><strong>Provider-Prüfungen:</strong> Prüft Endpoint, API-Key, Modellliste und * <li><strong>Provider-Prüfungen:</strong> Prüft Endpoint, API-Key, Modellliste und
* Modellplausibilität über den {@link ProviderTechnicalTestService}. Erzeugt Ergebnisse für * Modellplausibilität über den {@link ProviderTechnicalTestService}. Erzeugt Ergebnisse für
* {@link CheckpointId#BASE_URL_REACHABLE}, {@link CheckpointId#API_KEY_PRESENT}, * {@link CheckpointId#BASE_URL_REACHABLE}, {@link CheckpointId#API_KEY_PRESENT},
@@ -38,7 +40,7 @@ import de.gecheckt.pdf.umbenenner.application.validation.editor.EditorValidation
* ausgeführt, auch wenn ein Block eine Exception wirft. In diesem Fall werden die * ausgeführt, auch wenn ein Block eine Exception wirft. In diesem Fall werden die
* betroffenen Checkpoints als {@link CheckpointResult.Failure} mit Schweregrad ERROR * betroffenen Checkpoints als {@link CheckpointResult.Failure} mit Schweregrad ERROR
* und dem Präfix Interner Fehler:" markiert. Der Gesamtbericht enthält immer genau * und dem Präfix Interner Fehler:" markiert. Der Gesamtbericht enthält immer genau
* elf Einträge. * zwölf Einträge.
* <p> * <p>
* <strong>Threading-Kontrakt:</strong> Die Methode {@link #run(TechnicalTestRequest)} * <strong>Threading-Kontrakt:</strong> Die Methode {@link #run(TechnicalTestRequest)}
* ist synchron blockierend (der Provider-Prüfblock führt HTTP-Aufrufe durch). Sie darf * ist synchron blockierend (der Provider-Prüfblock führt HTTP-Aufrufe durch). Sie darf
@@ -51,28 +53,32 @@ import de.gecheckt.pdf.umbenenner.application.validation.editor.EditorValidation
* wird {@code config/prompt.txt} relativ zum Arbeitsverzeichnis verwendet. * wird {@code config/prompt.txt} relativ zum Arbeitsverzeichnis verwendet.
* <p> * <p>
* Dieser Service enthält keine JavaFX-Typen, keine NIO-Pfadobjekte in Signaturen und * Dieser Service enthält keine JavaFX-Typen, keine NIO-Pfadobjekte in Signaturen und
* keine Infrastrukturabhängigkeiten jenseits der drei injizierten Abhängigkeiten. * keine Infrastrukturabhängigkeiten jenseits der vier injizierten Abhängigkeiten.
*/ */
public class TechnicalTestOrchestrator { public class TechnicalTestOrchestrator {
private final EditorConfigurationValidator editorValidator; private final EditorConfigurationValidator editorValidator;
private final PathCheckPort pathCheckPort; private final PathCheckPort pathCheckPort;
private final ProviderTechnicalTestService providerTestService; private final ProviderTechnicalTestService providerTestService;
private final LogDiagnosticsPort logDiagnosticsPort;
/** /**
* Erstellt einen neuen Orchestrator mit den drei erforderlichen Abhängigkeiten. * Erstellt einen neuen Orchestrator mit den vier erforderlichen Abhängigkeiten.
* *
* @param editorValidator Lokaler Konfigurationsvalidator; darf nicht {@code null} sein * @param editorValidator Lokaler Konfigurationsvalidator; darf nicht {@code null} sein
* @param pathCheckPort Port für Dateisystem-Pfadprüfungen; darf nicht {@code null} sein * @param pathCheckPort Port für Dateisystem-Pfadprüfungen; darf nicht {@code null} sein
* @param providerTestService Service für provider-nahe technische Prüfungen; darf nicht {@code null} sein * @param providerTestService Service für provider-nahe technische Prüfungen; darf nicht {@code null} sein
* @param logDiagnosticsPort Port zur Auflösung des aktiven Log-Dateipfads; darf nicht {@code null} sein
* @throws NullPointerException wenn einer der Parameter {@code null} ist * @throws NullPointerException wenn einer der Parameter {@code null} ist
*/ */
public TechnicalTestOrchestrator(EditorConfigurationValidator editorValidator, public TechnicalTestOrchestrator(EditorConfigurationValidator editorValidator,
PathCheckPort pathCheckPort, PathCheckPort pathCheckPort,
ProviderTechnicalTestService providerTestService) { ProviderTechnicalTestService providerTestService,
LogDiagnosticsPort logDiagnosticsPort) {
this.editorValidator = Objects.requireNonNull(editorValidator, "editorValidator must not be null"); this.editorValidator = Objects.requireNonNull(editorValidator, "editorValidator must not be null");
this.pathCheckPort = Objects.requireNonNull(pathCheckPort, "pathCheckPort must not be null"); this.pathCheckPort = Objects.requireNonNull(pathCheckPort, "pathCheckPort must not be null");
this.providerTestService = Objects.requireNonNull(providerTestService, "providerTestService must not be null"); this.providerTestService = Objects.requireNonNull(providerTestService, "providerTestService must not be null");
this.logDiagnosticsPort = Objects.requireNonNull(logDiagnosticsPort, "logDiagnosticsPort must not be null");
} }
/** /**
@@ -80,7 +86,7 @@ public class TechnicalTestOrchestrator {
* <p> * <p>
* Alle drei Prüfblöcke werden immer vollständig ausgeführt. Ein Fehler in einem Block * Alle drei Prüfblöcke werden immer vollständig ausgeführt. Ein Fehler in einem Block
* führt nicht dazu, dass ein anderer Block übersprungen wird. Der zurückgegebene Bericht * führt nicht dazu, dass ein anderer Block übersprungen wird. Der zurückgegebene Bericht
* enthält immer genau elf {@link CheckpointResult}-Einträge. * enthält immer genau zwölf {@link CheckpointResult}-Einträge.
* <p> * <p>
* <strong>Prompt-Datei-Standardpfad:</strong> Wenn der Editorzustand keinen Prompt-Pfad * <strong>Prompt-Datei-Standardpfad:</strong> Wenn der Editorzustand keinen Prompt-Pfad
* enthält, wird als Standardpfad der Elternordner der Konfigurationsdatei gewählt * enthält, wird als Standardpfad der Elternordner der Konfigurationsdatei gewählt
@@ -96,7 +102,7 @@ public class TechnicalTestOrchestrator {
* abgeschlossen sind. Sie darf nicht auf dem JavaFX Application Thread aufgerufen werden. * abgeschlossen sind. Sie darf nicht auf dem JavaFX Application Thread aufgerufen werden.
* *
* @param request Eingabedaten für den Gesamttest; darf nicht {@code null} sein * @param request Eingabedaten für den Gesamttest; darf nicht {@code null} sein
* @return vollständiger Gesamttestbericht mit genau elf Einträgen; nie {@code null} * @return vollständiger Gesamttestbericht mit genau zwölf Einträgen; nie {@code null}
* @throws NullPointerException wenn {@code request} {@code null} ist * @throws NullPointerException wenn {@code request} {@code null} ist
*/ */
public TechnicalTestReport run(TechnicalTestRequest request) { public TechnicalTestReport run(TechnicalTestRequest request) {
@@ -104,13 +110,13 @@ public class TechnicalTestOrchestrator {
Instant startTime = Instant.now(); Instant startTime = Instant.now();
EditorValidationInput input = request.validationInput(); EditorValidationInput input = request.validationInput();
List<CheckpointResult> results = new ArrayList<>(11); List<CheckpointResult> results = new ArrayList<>(12);
// Block 1: Lokale Konfigurationsvalidierung (kein I/O) // Block 1: Lokale Konfigurationsvalidierung (kein I/O)
results.addAll(runLocalValidationBlock(input)); results.addAll(runLocalValidationBlock(input));
// Block 2: Pfadprüfungen (Dateisystem-I/O) // Block 2: Pfadprüfungen (Dateisystem-I/O)
results.addAll(runPathCheckBlock(input, request.configFilePath())); results.addAll(runPathCheckBlock(input, request.configFilePath(), request.logDirectory()));
// Block 3: Provider-nahe technische Prüfungen (Netzwerk-I/O) // Block 3: Provider-nahe technische Prüfungen (Netzwerk-I/O)
results.addAll(runProviderCheckBlock(input)); results.addAll(runProviderCheckBlock(input));
@@ -222,25 +228,30 @@ public class TechnicalTestOrchestrator {
// ========================================================================= // =========================================================================
/** /**
* Führt die Dateisystem-Pfadprüfungen für Prompt-Datei, Quellordner, Zielordner * Führt die Dateisystem-Pfadprüfungen für Prompt-Datei, Quellordner, Zielordner,
* und SQLite-Pfad durch. * SQLite-Pfad und Log-Verzeichnis durch.
* <p> * <p>
* Der {@code configFilePath} wird genutzt, um bei fehlendem Prompt-Pfad im Editorzustand * Der {@code configFilePath} wird genutzt, um bei fehlendem Prompt-Pfad im Editorzustand
* einen sinnvollen Standardpfad zu bestimmen ({@code <config-parent>/prompt.txt}). * einen sinnvollen Standardpfad zu bestimmen ({@code <config-parent>/prompt.txt}).
* Der {@code logDirectory} ist der konfigurierte Rohwert von {@code log.directory};
* leer bedeutet Standardwert {@code ./logs/}.
* *
* @param input aktueller Editorzustand * @param input aktueller Editorzustand
* @param configFilePath Pfad der geladenen Konfigurationsdatei; leer wenn keine geladen * @param configFilePath Pfad der geladenen Konfigurationsdatei; leer wenn keine geladen
* @return Liste mit genau vier Einträgen * @param logDirectory konfigurierter Rohwert von {@code log.directory}; leer = Standard
* @return Liste mit genau fünf Einträgen
*/ */
private List<CheckpointResult> runPathCheckBlock(EditorValidationInput input, private List<CheckpointResult> runPathCheckBlock(EditorValidationInput input,
String configFilePath) { String configFilePath,
String logDirectory) {
try { try {
List<CheckpointResult> results = new ArrayList<>(4); List<CheckpointResult> results = new ArrayList<>(5);
results.add(checkPromptFile(input.promptTemplateFile(), configFilePath, results.add(checkPromptFile(input.promptTemplateFile(), configFilePath,
resolveMaxTitleLengthForPromptCreation(input.maxTitleLength()))); resolveMaxTitleLengthForPromptCreation(input.maxTitleLength())));
results.add(checkSourceFolder(input.sourceFolder())); results.add(checkSourceFolder(input.sourceFolder()));
results.add(checkTargetFolder(input.targetFolder())); results.add(checkTargetFolder(input.targetFolder()));
results.add(checkSqlitePath(input.sqliteFile())); results.add(checkSqlitePath(input.sqliteFile()));
results.add(checkLogDirectory(logDirectory));
return results; return results;
} catch (Exception e) { } catch (Exception e) {
String errorMsg = "Interner Fehler bei den Pfadprüfungen: " + e.getMessage(); String errorMsg = "Interner Fehler bei den Pfadprüfungen: " + e.getMessage();
@@ -252,6 +263,8 @@ public class TechnicalTestOrchestrator {
CheckpointResult.Failure.of(CheckpointId.TARGET_FOLDER_USABLE, CheckpointResult.Failure.of(CheckpointId.TARGET_FOLDER_USABLE,
CheckpointSeverity.ERROR, errorMsg), CheckpointSeverity.ERROR, errorMsg),
CheckpointResult.Failure.of(CheckpointId.SQLITE_PATH_USABLE, CheckpointResult.Failure.of(CheckpointId.SQLITE_PATH_USABLE,
CheckpointSeverity.ERROR, errorMsg),
CheckpointResult.Failure.of(CheckpointId.LOG_DIRECTORY_USABLE,
CheckpointSeverity.ERROR, errorMsg) CheckpointSeverity.ERROR, errorMsg)
); );
} }
@@ -471,6 +484,64 @@ public class TechnicalTestOrchestrator {
suggestion); suggestion);
} }
/**
* Prüft das Log-Verzeichnis auf Schreibbarkeit und zeigt den tatsächlichen
* Log-Dateipfad aus der aktiven Log4j2-Konfiguration an.
* <p>
* <strong>Verzeichnis-Ermittlung:</strong> Wenn der konfigurierte {@code log.directory}-Wert
* leer ist, wird der Standard {@code ./logs} relativ zum Arbeitsverzeichnis angenommen.
* Der Wert wird zu einem absoluten Pfad aufgelöst.
* <p>
* <strong>Ergebnis:</strong>
* <ul>
* <li>{@link CheckpointResult.Success}: Verzeichnis ist vorhanden und schreibbar.
* Die Meldung enthält den aufgelösten absoluten Pfad sowie sofern ermittelbar
* den tatsächlichen Log-Dateipfad aus Log4j2.</li>
* <li>{@link CheckpointResult.Failure} mit Schweregrad {@link CheckpointSeverity#WARNING}:
* Verzeichnis ist nicht vorhanden oder nicht schreibbar. Ein nicht beschreibbares
* Log-Verzeichnis ist eine Warnung, kein harter Fehler, da die Anwendung auch ohne
* Datei-Logging lauffähig ist. Die Meldung enthält Konfiguration und aufgelösten
* absoluten Pfad als Diagnoseinformation.</li>
* </ul>
*
* @param configuredLogDir konfigurierter Rohwert von {@code log.directory}; leer = Standard
* @return Prüfpunkt-Ergebnis
*/
private CheckpointResult checkLogDirectory(String configuredLogDir) {
String effectiveDir = (configuredLogDir == null || configuredLogDir.isBlank())
? "./logs" : configuredLogDir;
String absolutePath;
try {
absolutePath = Paths.get(effectiveDir).toAbsolutePath().toString();
} catch (java.nio.file.InvalidPathException e) {
return CheckpointResult.Failure.of(CheckpointId.LOG_DIRECTORY_USABLE,
CheckpointSeverity.WARNING,
"Log-Verzeichnis: ungültiger Pfad: " + effectiveDir);
}
boolean configuredExplicitly = configuredLogDir != null && !configuredLogDir.isBlank();
String configLabel = configuredExplicitly
? "konfiguriert: " + configuredLogDir + ""
: "Standard → ";
java.util.Optional<String> activeLogFile = logDiagnosticsPort.resolveActiveLogFilePath();
String logFileInfo = activeLogFile
.map(p -> " | Aktive Log-Datei: " + p)
.orElse("");
if (pathCheckPort.isDirectoryWritableOrCreatable(absolutePath)) {
return new CheckpointResult.Success(CheckpointId.LOG_DIRECTORY_USABLE,
"Log-Verzeichnis beschreibbar (" + configLabel + absolutePath + ")" + logFileInfo);
}
return CheckpointResult.Failure.of(CheckpointId.LOG_DIRECTORY_USABLE,
CheckpointSeverity.WARNING,
"Log-Verzeichnis nicht beschreibbar (" + configLabel + absolutePath + ")"
+ logFileInfo
+ ". Tipp: Absoluten Pfad mit Forward-Slashes verwenden, z. B. C:/Benutzer/Logs");
}
// ========================================================================= // =========================================================================
// Block 3: Provider-nahe technische Prüfungen // Block 3: Provider-nahe technische Prüfungen
// ========================================================================= // =========================================================================
@@ -15,36 +15,44 @@ import de.gecheckt.pdf.umbenenner.application.validation.editor.EditorValidation
* Gesamttest, bei der automatischen Prompt-Erzeugung den Standardpfad relativ zur * Gesamttest, bei der automatischen Prompt-Erzeugung den Standardpfad relativ zur
* Konfigurationsdatei zu bestimmen. Er ist leer, wenn keine Konfigurationsdatei geladen ist. * Konfigurationsdatei zu bestimmen. Er ist leer, wenn keine Konfigurationsdatei geladen ist.
* <p> * <p>
* Das {@code logDirectory}-Feld trägt den konfigurierten Rohwert von {@code log.directory}
* aus dem Editor; leer bedeutet Standardwert {@code ./logs/}.
* <p>
* Dieser Record enthält keine JavaFX-Typen und keine Infrastrukturabhängigkeiten. * Dieser Record enthält keine JavaFX-Typen und keine Infrastrukturabhängigkeiten.
* *
* @param validationInput aktueller Editorzustand; nie {@code null} * @param validationInput aktueller Editorzustand; nie {@code null}
* @param configFilePath optionaler Pfad der geladenen Konfigurationsdatei als String; * @param configFilePath optionaler Pfad der geladenen Konfigurationsdatei als String;
* leer wenn keine Datei geladen ist * leer wenn keine Datei geladen ist
* @param logDirectory konfigurierter Rohwert von {@code log.directory};
* leer wenn kein Wert konfiguriert ist (Standard {@code ./logs/})
*/ */
public record TechnicalTestRequest( public record TechnicalTestRequest(
EditorValidationInput validationInput, EditorValidationInput validationInput,
String configFilePath) { String configFilePath,
String logDirectory) {
/** /**
* Erstellt eine neue Gesamttest-Anforderung. * Erstellt eine neue Gesamttest-Anforderung.
* *
* @param validationInput aktueller Editorzustand; darf nicht {@code null} sein * @param validationInput aktueller Editorzustand; darf nicht {@code null} sein
* @param configFilePath Pfad der Konfigurationsdatei; {@code null} wird zu leerem String * @param configFilePath Pfad der Konfigurationsdatei; {@code null} wird zu leerem String
* @param logDirectory Rohwert von {@code log.directory}; {@code null} wird zu leerem String
* @throws NullPointerException wenn {@code validationInput} {@code null} ist * @throws NullPointerException wenn {@code validationInput} {@code null} ist
*/ */
public TechnicalTestRequest { public TechnicalTestRequest {
Objects.requireNonNull(validationInput, "validationInput must not be null"); Objects.requireNonNull(validationInput, "validationInput must not be null");
configFilePath = configFilePath == null ? "" : configFilePath; configFilePath = configFilePath == null ? "" : configFilePath;
logDirectory = logDirectory == null ? "" : logDirectory;
} }
/** /**
* Erstellt eine Anforderung ohne geladene Konfigurationsdatei. * Erstellt eine Anforderung ohne geladene Konfigurationsdatei und ohne Log-Verzeichnis-Angabe.
* *
* @param validationInput aktueller Editorzustand; darf nicht {@code null} sein * @param validationInput aktueller Editorzustand; darf nicht {@code null} sein
* @return eine neue Anforderung ohne Konfigurationsdateipfad * @return eine neue Anforderung ohne Konfigurationsdateipfad
*/ */
public static TechnicalTestRequest of(EditorValidationInput validationInput) { public static TechnicalTestRequest of(EditorValidationInput validationInput) {
return new TechnicalTestRequest(validationInput, ""); return new TechnicalTestRequest(validationInput, "", "");
} }
/** /**
@@ -23,13 +23,14 @@ class CheckpointIdTest {
CheckpointId.PROMPT_FILE_PRESENT, CheckpointId.PROMPT_FILE_PRESENT,
CheckpointId.SOURCE_FOLDER_PRESENT, CheckpointId.SOURCE_FOLDER_PRESENT,
CheckpointId.TARGET_FOLDER_USABLE, CheckpointId.TARGET_FOLDER_USABLE,
CheckpointId.SQLITE_PATH_USABLE CheckpointId.SQLITE_PATH_USABLE,
CheckpointId.LOG_DIRECTORY_USABLE
); );
} }
@Test @Test
void enumHasExactlyElevenValues() { void enumHasTwelveValues() {
assertThat(CheckpointId.values()).hasSize(11); assertThat(CheckpointId.values()).hasSize(12);
} }
@Test @Test
@@ -15,7 +15,7 @@ import de.gecheckt.pdf.umbenenner.application.validation.editor.EditorValidation
* <p> * <p>
* Prüft insbesondere, dass alle drei Blöcke immer vollständig durchlaufen werden * Prüft insbesondere, dass alle drei Blöcke immer vollständig durchlaufen werden
* (kein Frühabbruch), auch wenn ein Block eine Exception wirft, und dass der * (kein Frühabbruch), auch wenn ein Block eine Exception wirft, und dass der
* zurückgegebene Bericht immer genau elf Einträge enthält. * zurückgegebene Bericht immer genau zwölf Einträge enthält.
*/ */
class TechnicalTestOrchestratorTest { class TechnicalTestOrchestratorTest {
@@ -88,6 +88,11 @@ class TechnicalTestOrchestratorTest {
(family, propertyValue) -> EffectiveApiKeyDescriptor.fromPropertyFile()); (family, propertyValue) -> EffectiveApiKeyDescriptor.fromPropertyFile());
} }
/** No-op {@link LogDiagnosticsPort}: gibt immer einen leeren Optional zurück. */
private static LogDiagnosticsPort noOpLogDiagnosticsPort() {
return () -> java.util.Optional.empty();
}
private static CheckpointResult findById(List<CheckpointResult> results, CheckpointId id) { private static CheckpointResult findById(List<CheckpointResult> results, CheckpointId id) {
return results.stream() return results.stream()
.filter(r -> r.checkpointId() == id) .filter(r -> r.checkpointId() == id)
@@ -98,19 +103,20 @@ class TechnicalTestOrchestratorTest {
// ------------------------------------------------------------------ Tests: Vollständig grüner Pfad // ------------------------------------------------------------------ Tests: Vollständig grüner Pfad
/** /**
* Alle drei Blöcke liefern Erfolg: der Bericht enthält genau 11 Success-Einträge. * Alle drei Blöcke liefern Erfolg: der Bericht enthält genau 12 Success-Einträge.
*/ */
@Test @Test
void allBlocksSucceed_reportContainsElevenSuccessEntries() { void allBlocksSucceed_reportContainsTwelveSuccessEntries() {
TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator( TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator(
new EditorConfigurationValidator(), new EditorConfigurationValidator(),
allOkPathCheckPort(), allOkPathCheckPort(),
allSuccessProviderService()); allSuccessProviderService(),
noOpLogDiagnosticsPort());
TechnicalTestReport report = orchestrator.run( TechnicalTestReport report = orchestrator.run(
TechnicalTestRequest.of(validClaudeInput())); TechnicalTestRequest.of(validClaudeInput()));
assertThat(report.size()).isEqualTo(11); assertThat(report.size()).isEqualTo(12);
assertThat(report.results()) assertThat(report.results())
.allSatisfy(r -> assertThat(r).isInstanceOf(CheckpointResult.Success.class)); .allSatisfy(r -> assertThat(r).isInstanceOf(CheckpointResult.Success.class));
assertThat(report.hasErrors()).isFalse(); assertThat(report.hasErrors()).isFalse();
@@ -120,19 +126,20 @@ class TechnicalTestOrchestratorTest {
// ------------------------------------------------------------------ Tests: Kein Frühabbruch // ------------------------------------------------------------------ Tests: Kein Frühabbruch
/** /**
* Bericht enthält immer genau 11 Einträge, auch wenn Block 2 und Block 3 alle Fehler liefern. * Bericht enthält immer genau 12 Einträge, auch wenn Block 2 und Block 3 alle Fehler liefern.
*/ */
@Test @Test
void alwaysElevenCheckpointsInReport_evenWithFailures() { void alwaysTwelveCheckpointsInReport_evenWithFailures() {
TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator( TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator(
new EditorConfigurationValidator(), new EditorConfigurationValidator(),
noOpPathCheckPort(), // Block 2: alle Pfade nicht vorhanden noOpPathCheckPort(), // Block 2: alle Pfade nicht vorhanden
throwingProviderService()); // Block 3: Exception throwingProviderService(), // Block 3: Exception
noOpLogDiagnosticsPort());
TechnicalTestReport report = orchestrator.run( TechnicalTestReport report = orchestrator.run(
TechnicalTestRequest.of(emptyProviderInput())); TechnicalTestRequest.of(emptyProviderInput()));
assertThat(report.size()).isEqualTo(11); assertThat(report.size()).isEqualTo(12);
} }
// ------------------------------------------------------------------ Tests: Block 1 - Exception führt zu Failure // ------------------------------------------------------------------ Tests: Block 1 - Exception führt zu Failure
@@ -155,12 +162,13 @@ class TechnicalTestOrchestratorTest {
TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator( TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator(
throwingValidator, throwingValidator,
allOkPathCheckPort(), allOkPathCheckPort(),
allSuccessProviderService()); allSuccessProviderService(),
noOpLogDiagnosticsPort());
TechnicalTestReport report = orchestrator.run( TechnicalTestReport report = orchestrator.run(
TechnicalTestRequest.of(validClaudeInput())); TechnicalTestRequest.of(validClaudeInput()));
assertThat(report.size()).isEqualTo(11); assertThat(report.size()).isEqualTo(12);
// Block-1-Checkpoints müssen Failure mit "Interner Fehler" sein // Block-1-Checkpoints müssen Failure mit "Interner Fehler" sein
CheckpointResult basicValidation = findById(report.results(), CheckpointResult basicValidation = findById(report.results(),
@@ -197,12 +205,13 @@ class TechnicalTestOrchestratorTest {
TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator( TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator(
new EditorConfigurationValidator(), new EditorConfigurationValidator(),
allOkPathCheckPort(), allOkPathCheckPort(),
throwingProviderService()); throwingProviderService(),
noOpLogDiagnosticsPort());
TechnicalTestReport report = orchestrator.run( TechnicalTestReport report = orchestrator.run(
TechnicalTestRequest.of(validClaudeInput())); TechnicalTestRequest.of(validClaudeInput()));
assertThat(report.size()).isEqualTo(11); assertThat(report.size()).isEqualTo(12);
// Block-3-Checkpoints müssen Failure mit "Interner Fehler" sein // Block-3-Checkpoints müssen Failure mit "Interner Fehler" sein
List<CheckpointId> block3Ids = List.of( List<CheckpointId> block3Ids = List.of(
@@ -250,7 +259,8 @@ class TechnicalTestOrchestratorTest {
TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator( TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator(
new EditorConfigurationValidator(), new EditorConfigurationValidator(),
pathPort, pathPort,
allSuccessProviderService()); allSuccessProviderService(),
noOpLogDiagnosticsPort());
TechnicalTestReport report = orchestrator.run( TechnicalTestReport report = orchestrator.run(
TechnicalTestRequest.of(validClaudeInput())); TechnicalTestRequest.of(validClaudeInput()));
@@ -280,7 +290,8 @@ class TechnicalTestOrchestratorTest {
TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator( TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator(
new EditorConfigurationValidator(), new EditorConfigurationValidator(),
pathPort, pathPort,
allSuccessProviderService()); allSuccessProviderService(),
noOpLogDiagnosticsPort());
TechnicalTestReport report = orchestrator.run( TechnicalTestReport report = orchestrator.run(
TechnicalTestRequest.of(validClaudeInput())); TechnicalTestRequest.of(validClaudeInput()));
@@ -310,7 +321,8 @@ class TechnicalTestOrchestratorTest {
TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator( TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator(
new EditorConfigurationValidator(), new EditorConfigurationValidator(),
pathPort, pathPort,
allSuccessProviderService()); allSuccessProviderService(),
noOpLogDiagnosticsPort());
TechnicalTestReport report = orchestrator.run( TechnicalTestReport report = orchestrator.run(
TechnicalTestRequest.of(validClaudeInput())); TechnicalTestRequest.of(validClaudeInput()));
@@ -327,14 +339,15 @@ class TechnicalTestOrchestratorTest {
// ------------------------------------------------------------------ Tests: Korrekte Checkpoint-Reihenfolge // ------------------------------------------------------------------ Tests: Korrekte Checkpoint-Reihenfolge
/** /**
* Der Bericht enthält genau die erwarteten 11 Checkpoint-IDs. * Der Bericht enthält genau die erwarteten 12 Checkpoint-IDs.
*/ */
@Test @Test
void report_containsAllExpectedCheckpointIds() { void report_containsAllExpectedCheckpointIds() {
TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator( TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator(
new EditorConfigurationValidator(), new EditorConfigurationValidator(),
noOpPathCheckPort(), noOpPathCheckPort(),
allSuccessProviderService()); allSuccessProviderService(),
noOpLogDiagnosticsPort());
TechnicalTestReport report = orchestrator.run( TechnicalTestReport report = orchestrator.run(
TechnicalTestRequest.of(validClaudeInput())); TechnicalTestRequest.of(validClaudeInput()));
@@ -350,6 +363,7 @@ class TechnicalTestOrchestratorTest {
CheckpointId.SOURCE_FOLDER_PRESENT, CheckpointId.SOURCE_FOLDER_PRESENT,
CheckpointId.TARGET_FOLDER_USABLE, CheckpointId.TARGET_FOLDER_USABLE,
CheckpointId.SQLITE_PATH_USABLE, CheckpointId.SQLITE_PATH_USABLE,
CheckpointId.LOG_DIRECTORY_USABLE,
CheckpointId.API_KEY_PRESENT, CheckpointId.API_KEY_PRESENT,
CheckpointId.BASE_URL_REACHABLE, CheckpointId.BASE_URL_REACHABLE,
CheckpointId.API_KEY_ACCEPTED, CheckpointId.API_KEY_ACCEPTED,
@@ -387,11 +401,12 @@ class TechnicalTestOrchestratorTest {
TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator( TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator(
new EditorConfigurationValidator(), new EditorConfigurationValidator(),
pathPort, pathPort,
allSuccessProviderService()); allSuccessProviderService(),
noOpLogDiagnosticsPort());
// configFilePath = C:/config/application.properties Elternordner = C:/config // configFilePath = C:/config/application.properties Elternordner = C:/config
TechnicalTestReport report = orchestrator.run( TechnicalTestReport report = orchestrator.run(
new TechnicalTestRequest(inputWithoutPrompt, "C:/config/application.properties")); new TechnicalTestRequest(inputWithoutPrompt, "C:/config/application.properties", ""));
CheckpointResult promptResult = findById(report.results(), CheckpointId.PROMPT_FILE_PRESENT); CheckpointResult promptResult = findById(report.results(), CheckpointId.PROMPT_FILE_PRESENT);
assertThat(promptResult).isInstanceOf(CheckpointResult.Failure.class); assertThat(promptResult).isInstanceOf(CheckpointResult.Failure.class);
@@ -431,7 +446,8 @@ class TechnicalTestOrchestratorTest {
TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator( TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator(
new EditorConfigurationValidator(), new EditorConfigurationValidator(),
pathPort, pathPort,
allSuccessProviderService()); allSuccessProviderService(),
noOpLogDiagnosticsPort());
// Kein configFilePath Fallback auf config/prompt.txt // Kein configFilePath Fallback auf config/prompt.txt
TechnicalTestReport report = orchestrator.run( TechnicalTestReport report = orchestrator.run(
@@ -462,7 +478,8 @@ class TechnicalTestOrchestratorTest {
TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator( TechnicalTestOrchestrator orchestrator = new TechnicalTestOrchestrator(
new EditorConfigurationValidator(), new EditorConfigurationValidator(),
pathPort, pathPort,
allSuccessProviderService()); allSuccessProviderService(),
noOpLogDiagnosticsPort());
TechnicalTestReport report = orchestrator.run( TechnicalTestReport report = orchestrator.run(
TechnicalTestRequest.of(validClaudeInput())); TechnicalTestRequest.of(validClaudeInput()));
@@ -29,18 +29,30 @@ class TechnicalTestRequestTest {
@Test @Test
void withConfigFilePath_detectedAsPresent() { void withConfigFilePath_detectedAsPresent() {
var request = new TechnicalTestRequest(minimalInput(), "/config/app.properties"); var request = new TechnicalTestRequest(minimalInput(), "/config/app.properties", "");
assertThat(request.hasConfigFilePath()).isTrue(); assertThat(request.hasConfigFilePath()).isTrue();
assertThat(request.configFilePath()).isEqualTo("/config/app.properties"); assertThat(request.configFilePath()).isEqualTo("/config/app.properties");
} }
@Test @Test
void nullConfigFilePath_normalisedToEmpty() { void nullConfigFilePath_normalisedToEmpty() {
var request = new TechnicalTestRequest(minimalInput(), null); var request = new TechnicalTestRequest(minimalInput(), null, null);
assertThat(request.configFilePath()).isEmpty(); assertThat(request.configFilePath()).isEmpty();
assertThat(request.hasConfigFilePath()).isFalse(); assertThat(request.hasConfigFilePath()).isFalse();
} }
@Test
void withLogDirectory_preservedAsIs() {
var request = new TechnicalTestRequest(minimalInput(), "", "C:/logs");
assertThat(request.logDirectory()).isEqualTo("C:/logs");
}
@Test
void nullLogDirectory_normalisedToEmpty() {
var request = new TechnicalTestRequest(minimalInput(), "", null);
assertThat(request.logDirectory()).isEmpty();
}
@Test @Test
void nullValidationInputThrows() { void nullValidationInputThrows() {
assertThatNullPointerException() assertThatNullPointerException()
@@ -109,6 +109,7 @@ import de.gecheckt.pdf.umbenenner.application.validation.technicaltest.ProviderT
import de.gecheckt.pdf.umbenenner.application.validation.technicaltest.TechnicalTestOrchestrator; import de.gecheckt.pdf.umbenenner.application.validation.technicaltest.TechnicalTestOrchestrator;
import de.gecheckt.pdf.umbenenner.bootstrap.adapter.AiModelCatalogDispatcher; import de.gecheckt.pdf.umbenenner.bootstrap.adapter.AiModelCatalogDispatcher;
import de.gecheckt.pdf.umbenenner.bootstrap.adapter.GuiConfigurationPropertiesWriter; import de.gecheckt.pdf.umbenenner.bootstrap.adapter.GuiConfigurationPropertiesWriter;
import de.gecheckt.pdf.umbenenner.bootstrap.adapter.Log4jLogDiagnosticsAdapter;
import de.gecheckt.pdf.umbenenner.bootstrap.adapter.Log4jProcessingLogger; import de.gecheckt.pdf.umbenenner.bootstrap.adapter.Log4jProcessingLogger;
import de.gecheckt.pdf.umbenenner.bootstrap.singleinstance.AnotherInstanceRunningException; import de.gecheckt.pdf.umbenenner.bootstrap.singleinstance.AnotherInstanceRunningException;
import de.gecheckt.pdf.umbenenner.bootstrap.singleinstance.SingleInstanceGuard; import de.gecheckt.pdf.umbenenner.bootstrap.singleinstance.SingleInstanceGuard;
@@ -807,7 +808,8 @@ public class BootstrapRunner {
TechnicalTestOrchestrator technicalTestOrchestrator = new TechnicalTestOrchestrator( TechnicalTestOrchestrator technicalTestOrchestrator = new TechnicalTestOrchestrator(
new EditorConfigurationValidator(), new EditorConfigurationValidator(),
pathCheckPort, pathCheckPort,
providerTechnicalTestService); providerTechnicalTestService,
new Log4jLogDiagnosticsAdapter());
de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService correctionExecutionService = de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService correctionExecutionService =
new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService( new de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionExecutionService(
new de.gecheckt.pdf.umbenenner.adapter.out.resourcecreation.FilesystemResourceCreationAdapter()); new de.gecheckt.pdf.umbenenner.adapter.out.resourcecreation.FilesystemResourceCreationAdapter());
@@ -0,0 +1,68 @@
package de.gecheckt.pdf.umbenenner.bootstrap.adapter;
import java.nio.file.InvalidPathException;
import java.nio.file.Paths;
import java.util.Optional;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.RollingFileAppender;
import de.gecheckt.pdf.umbenenner.application.validation.technicaltest.LogDiagnosticsPort;
/**
* Log4j2-basierte Implementierung von {@link LogDiagnosticsPort}.
* <p>
* Liest den aktiven {@link RollingFileAppender} aus dem laufenden {@link LoggerContext}
* und gibt dessen absoluten Dateipfad zurück. Diese Information zeigt, wo Log-Einträge
* tatsächlich landen unabhängig vom konfigurierten {@code log.directory}-Wert in der
* Properties-Datei.
* <p>
* Gibt einen leeren {@link Optional} zurück, wenn kein dateibasierter Appender aktiv ist,
* der Kontext nicht auslesbar ist oder der Pfad nicht zu einem absoluten Pfad aufgelöst
* werden kann.
*/
public class Log4jLogDiagnosticsAdapter implements LogDiagnosticsPort {
/**
* Erstellt einen neuen {@code Log4jLogDiagnosticsAdapter}.
*/
public Log4jLogDiagnosticsAdapter() {
// zustandslos
}
/**
* Ermittelt den absoluten Pfad der aktiven Log-Datei aus dem laufenden Log4j2-Kontext.
* <p>
* Durchsucht die Appender-Liste des aktiven {@link LoggerContext} nach dem ersten
* {@link RollingFileAppender} und gibt dessen {@code fileName} als absoluten Pfad zurück.
* Alle Ausnahmen werden abgefangen und als leeres Ergebnis zurückgegeben.
*
* @return absoluter Pfad der aktiven Log-Datei; leer wenn nicht bestimmbar
*/
@Override
public Optional<String> resolveActiveLogFilePath() {
try {
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
return ctx.getConfiguration().getAppenders().values().stream()
.filter(a -> a instanceof RollingFileAppender)
.map(a -> (RollingFileAppender) a)
.map(RollingFileAppender::getFileName)
.filter(name -> name != null && !name.isBlank())
.map(this::toAbsolutePath)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
} catch (Exception e) {
return Optional.empty();
}
}
private Optional<String> toAbsolutePath(String path) {
try {
return Optional.of(Paths.get(path).toAbsolutePath().toString());
} catch (InvalidPathException e) {
return Optional.empty();
}
}
}