#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:
+9
-1
@@ -84,5 +84,13 @@ public enum CheckpointId {
|
||||
* zeigt auf eine vorhandene Datei oder auf einen beschreibbaren Ordner, in dem die
|
||||
* 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
|
||||
}
|
||||
|
||||
+28
@@ -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();
|
||||
}
|
||||
+90
-19
@@ -17,16 +17,18 @@ import de.gecheckt.pdf.umbenenner.application.validation.editor.EditorValidation
|
||||
/**
|
||||
* Orchestrator für den vollständigen technischen Gesamttest der GUI-Konfiguration.
|
||||
* <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>
|
||||
* <li><strong>Lokale Validierung:</strong> Prüft den Editorzustand ohne I/O mithilfe des
|
||||
* {@link EditorConfigurationValidator}. Erzeugt Ergebnisse für
|
||||
* {@link CheckpointId#CONFIGURATION_BASIC_VALIDATION} und
|
||||
* {@link CheckpointId#PROVIDER_CONFIGURATION}.</li>
|
||||
* <li><strong>Pfadprüfungen:</strong> Prüft Quellordner, Zielordner, Prompt-Datei und
|
||||
* SQLite-Pfad über den {@link PathCheckPort}. Erzeugt Ergebnisse für
|
||||
* <li><strong>Pfadprüfungen:</strong> Prüft Quellordner, Zielordner, Prompt-Datei,
|
||||
* 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#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
|
||||
* Modellplausibilität über den {@link ProviderTechnicalTestService}. Erzeugt Ergebnisse für
|
||||
* {@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
|
||||
* betroffenen Checkpoints als {@link CheckpointResult.Failure} mit Schweregrad ERROR
|
||||
* und dem Präfix „Interner Fehler:" markiert. Der Gesamtbericht enthält immer genau
|
||||
* elf Einträge.
|
||||
* zwölf Einträge.
|
||||
* <p>
|
||||
* <strong>Threading-Kontrakt:</strong> Die Methode {@link #run(TechnicalTestRequest)}
|
||||
* 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.
|
||||
* <p>
|
||||
* 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 {
|
||||
|
||||
private final EditorConfigurationValidator editorValidator;
|
||||
private final PathCheckPort pathCheckPort;
|
||||
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 pathCheckPort Port für Dateisystem-Pfadprüfungen; 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 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
|
||||
*/
|
||||
public TechnicalTestOrchestrator(EditorConfigurationValidator editorValidator,
|
||||
PathCheckPort pathCheckPort,
|
||||
ProviderTechnicalTestService providerTestService) {
|
||||
ProviderTechnicalTestService providerTestService,
|
||||
LogDiagnosticsPort logDiagnosticsPort) {
|
||||
this.editorValidator = Objects.requireNonNull(editorValidator, "editorValidator 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.logDiagnosticsPort = Objects.requireNonNull(logDiagnosticsPort, "logDiagnosticsPort must not be null");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,7 +86,7 @@ public class TechnicalTestOrchestrator {
|
||||
* <p>
|
||||
* 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
|
||||
* enthält immer genau elf {@link CheckpointResult}-Einträge.
|
||||
* enthält immer genau zwölf {@link CheckpointResult}-Einträge.
|
||||
* <p>
|
||||
* <strong>Prompt-Datei-Standardpfad:</strong> Wenn der Editorzustand keinen Prompt-Pfad
|
||||
* 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.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
public TechnicalTestReport run(TechnicalTestRequest request) {
|
||||
@@ -104,13 +110,13 @@ public class TechnicalTestOrchestrator {
|
||||
Instant startTime = Instant.now();
|
||||
EditorValidationInput input = request.validationInput();
|
||||
|
||||
List<CheckpointResult> results = new ArrayList<>(11);
|
||||
List<CheckpointResult> results = new ArrayList<>(12);
|
||||
|
||||
// Block 1: Lokale Konfigurationsvalidierung (kein I/O)
|
||||
results.addAll(runLocalValidationBlock(input));
|
||||
|
||||
// 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)
|
||||
results.addAll(runProviderCheckBlock(input));
|
||||
@@ -222,25 +228,30 @@ public class TechnicalTestOrchestrator {
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Führt die Dateisystem-Pfadprüfungen für Prompt-Datei, Quellordner, Zielordner
|
||||
* und SQLite-Pfad durch.
|
||||
* Führt die Dateisystem-Pfadprüfungen für Prompt-Datei, Quellordner, Zielordner,
|
||||
* SQLite-Pfad und Log-Verzeichnis durch.
|
||||
* <p>
|
||||
* Der {@code configFilePath} wird genutzt, um bei fehlendem Prompt-Pfad im Editorzustand
|
||||
* 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 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,
|
||||
String configFilePath) {
|
||||
String configFilePath,
|
||||
String logDirectory) {
|
||||
try {
|
||||
List<CheckpointResult> results = new ArrayList<>(4);
|
||||
List<CheckpointResult> results = new ArrayList<>(5);
|
||||
results.add(checkPromptFile(input.promptTemplateFile(), configFilePath,
|
||||
resolveMaxTitleLengthForPromptCreation(input.maxTitleLength())));
|
||||
results.add(checkSourceFolder(input.sourceFolder()));
|
||||
results.add(checkTargetFolder(input.targetFolder()));
|
||||
results.add(checkSqlitePath(input.sqliteFile()));
|
||||
results.add(checkLogDirectory(logDirectory));
|
||||
return results;
|
||||
} catch (Exception e) {
|
||||
String errorMsg = "Interner Fehler bei den Pfadprüfungen: " + e.getMessage();
|
||||
@@ -252,6 +263,8 @@ public class TechnicalTestOrchestrator {
|
||||
CheckpointResult.Failure.of(CheckpointId.TARGET_FOLDER_USABLE,
|
||||
CheckpointSeverity.ERROR, errorMsg),
|
||||
CheckpointResult.Failure.of(CheckpointId.SQLITE_PATH_USABLE,
|
||||
CheckpointSeverity.ERROR, errorMsg),
|
||||
CheckpointResult.Failure.of(CheckpointId.LOG_DIRECTORY_USABLE,
|
||||
CheckpointSeverity.ERROR, errorMsg)
|
||||
);
|
||||
}
|
||||
@@ -471,6 +484,64 @@ public class TechnicalTestOrchestrator {
|
||||
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
|
||||
// =========================================================================
|
||||
|
||||
+11
-3
@@ -15,36 +15,44 @@ import de.gecheckt.pdf.umbenenner.application.validation.editor.EditorValidation
|
||||
* Gesamttest, bei der automatischen Prompt-Erzeugung den Standardpfad relativ zur
|
||||
* Konfigurationsdatei zu bestimmen. Er ist leer, wenn keine Konfigurationsdatei geladen ist.
|
||||
* <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.
|
||||
*
|
||||
* @param validationInput aktueller Editorzustand; nie {@code null}
|
||||
* @param configFilePath optionaler Pfad der geladenen Konfigurationsdatei als String;
|
||||
* 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(
|
||||
EditorValidationInput validationInput,
|
||||
String configFilePath) {
|
||||
String configFilePath,
|
||||
String logDirectory) {
|
||||
|
||||
/**
|
||||
* Erstellt eine neue Gesamttest-Anforderung.
|
||||
*
|
||||
* @param validationInput aktueller Editorzustand; darf nicht {@code null} sein
|
||||
* @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
|
||||
*/
|
||||
public TechnicalTestRequest {
|
||||
Objects.requireNonNull(validationInput, "validationInput must not be null");
|
||||
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
|
||||
* @return eine neue Anforderung ohne Konfigurationsdateipfad
|
||||
*/
|
||||
public static TechnicalTestRequest of(EditorValidationInput validationInput) {
|
||||
return new TechnicalTestRequest(validationInput, "");
|
||||
return new TechnicalTestRequest(validationInput, "", "");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user