fix: Neue Datenbank anlegen ohne geladene Konfiguration ermoeglichen
Ohne Override und ohne geladene Konfiguration lieferte der aktive-DB-Supplier faelschlich den Zielpfad selbst zurueck, wodurch die Selbst-Ueberschreib-Pruefung immer ansprach und jede Anlage mit "Zielpfad ist die aktuell aktive Datenbankdatei" abgelehnt wurde. Der Supplier signalisiert "keine aktive Datenbank" nun mit null; der Use-Case ueberspringt in diesem Fall die Pruefung und laesst die Anlage zu. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+15
-8
@@ -41,7 +41,9 @@ import de.gecheckt.pdf.umbenenner.application.port.out.DatabaseCreationPort;
|
|||||||
* Aktive DB und Zielpfad werden über {@link Path#toRealPath(java.nio.file.LinkOption...)}
|
* Aktive DB und Zielpfad werden über {@link Path#toRealPath(java.nio.file.LinkOption...)}
|
||||||
* normalisiert verglichen. Für noch nicht existierende Dateien wird das Elternverzeichnis
|
* normalisiert verglichen. Für noch nicht existierende Dateien wird das Elternverzeichnis
|
||||||
* real aufgelöst und der Dateiname normalisiert verglichen. Auf Windows erfolgt der
|
* real aufgelöst und der Dateiname normalisiert verglichen. Auf Windows erfolgt der
|
||||||
* Vergleich case-insensitive.
|
* Vergleich case-insensitive. Ist aktuell keine aktive Datenbank in Betrieb (der
|
||||||
|
* {@link ActiveDatabasePathSupplier} liefert {@code null}), entfällt die Prüfung und die
|
||||||
|
* Anlage fährt fort – es gibt dann keine in Benutzung befindliche Datei zu schützen.
|
||||||
*/
|
*/
|
||||||
public class DefaultCreateNewDatabaseUseCase implements CreateNewDatabaseUseCase {
|
public class DefaultCreateNewDatabaseUseCase implements CreateNewDatabaseUseCase {
|
||||||
|
|
||||||
@@ -66,7 +68,9 @@ public class DefaultCreateNewDatabaseUseCase implements CreateNewDatabaseUseCase
|
|||||||
/**
|
/**
|
||||||
* Liefert den Pfad der aktuell aktiven SQLite-Datei.
|
* Liefert den Pfad der aktuell aktiven SQLite-Datei.
|
||||||
*
|
*
|
||||||
* @return den absoluten Pfad der aktiven Datei; nie {@code null}
|
* @return den absoluten Pfad der aktiven Datei oder {@code null}, wenn aktuell keine
|
||||||
|
* aktive Datenbank in Betrieb ist. Bei {@code null} entfällt die
|
||||||
|
* Selbst-Überschreib-Prüfung und die Anlage darf fortfahren.
|
||||||
*/
|
*/
|
||||||
Path get();
|
Path get();
|
||||||
}
|
}
|
||||||
@@ -95,6 +99,9 @@ public class DefaultCreateNewDatabaseUseCase implements CreateNewDatabaseUseCase
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Orchestriert den vollständigen Anlage- und Wechselvorgang.
|
* Orchestriert den vollständigen Anlage- und Wechselvorgang.
|
||||||
|
* <p>
|
||||||
|
* Liefert der {@link ActiveDatabasePathSupplier} {@code null} (keine aktive Datenbank in
|
||||||
|
* Betrieb), entfällt die Selbst-Überschreib-Prüfung und die Anlage fährt fort.
|
||||||
*
|
*
|
||||||
* @param targetFile der vom Benutzer ausgewählte Zielpfad; darf nicht {@code null} sein
|
* @param targetFile der vom Benutzer ausgewählte Zielpfad; darf nicht {@code null} sein
|
||||||
* @return strukturiertes Ergebnis mit Erfolg oder klassifiziertem Fehler; nie {@code null}
|
* @return strukturiertes Ergebnis mit Erfolg oder klassifiziertem Fehler; nie {@code null}
|
||||||
@@ -106,14 +113,13 @@ public class DefaultCreateNewDatabaseUseCase implements CreateNewDatabaseUseCase
|
|||||||
LOG.info("Neue Datenbank anlegen: angeforderter Zielpfad = {}", absoluteTarget);
|
LOG.info("Neue Datenbank anlegen: angeforderter Zielpfad = {}", absoluteTarget);
|
||||||
|
|
||||||
// Schritt 1: Pfad-Sicherheitsprüfung
|
// Schritt 1: Pfad-Sicherheitsprüfung
|
||||||
|
// Ohne aktive Datenbank (Supplier liefert null) gibt es nichts zu schützen; die
|
||||||
|
// Selbst-Überschreib-Prüfung entfällt und die Anlage darf fortfahren.
|
||||||
Path activeDb = activeDatabasePathSupplier.get();
|
Path activeDb = activeDatabasePathSupplier.get();
|
||||||
if (activeDb == null) {
|
if (activeDb == null) {
|
||||||
LOG.error("Aktiver Datenbankpfad ist nicht ermittelbar – Anlage abgebrochen.");
|
LOG.info("Keine aktive Datenbank in Betrieb – Selbst-Überschreib-Prüfung entfällt, "
|
||||||
return new CreateNewDatabaseResult.CreationFailed(
|
+ "Anlage fährt fort.");
|
||||||
CreateNewDatabaseResult.Phase.PATH_RESOLUTION,
|
} else {
|
||||||
"Aktiver Datenbankpfad konnte nicht ermittelt werden.",
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
Path absoluteActive = activeDb.toAbsolutePath().normalize();
|
Path absoluteActive = activeDb.toAbsolutePath().normalize();
|
||||||
boolean sameFile;
|
boolean sameFile;
|
||||||
try {
|
try {
|
||||||
@@ -130,6 +136,7 @@ public class DefaultCreateNewDatabaseUseCase implements CreateNewDatabaseUseCase
|
|||||||
absoluteTarget);
|
absoluteTarget);
|
||||||
return new CreateNewDatabaseResult.SameAsActiveDatabase(absoluteTarget);
|
return new CreateNewDatabaseResult.SameAsActiveDatabase(absoluteTarget);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Schritt 2: Temp-Datei im Zielverzeichnis vorbereiten
|
// Schritt 2: Temp-Datei im Zielverzeichnis vorbereiten
|
||||||
Path parent = absoluteTarget.getParent();
|
Path parent = absoluteTarget.getParent();
|
||||||
|
|||||||
+57
@@ -168,6 +168,63 @@ class DefaultCreateNewDatabaseUseCaseTest {
|
|||||||
assertThat(contextOverride.get()).isEqualTo(target.toAbsolutePath().normalize());
|
assertThat(contextOverride.get()).isEqualTo(target.toAbsolutePath().normalize());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createNewDatabase_shouldProceed_whenNoActiveDatabase(@TempDir Path tempDir) throws IOException {
|
||||||
|
// Szenario: GUI ohne geladene Konfiguration und ohne Override → Supplier liefert null.
|
||||||
|
// Es gibt keine aktive DB zu schützen; die Anlage muss durchlaufen.
|
||||||
|
Path target = tempDir.resolve("neu.sqlite");
|
||||||
|
|
||||||
|
AtomicReference<Path> capturedTemp = new AtomicReference<>();
|
||||||
|
DatabaseCreationPort creation = tempFile -> {
|
||||||
|
try {
|
||||||
|
Files.writeString(tempFile, "migrated-content");
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
capturedTemp.set(tempFile);
|
||||||
|
return new DatabaseCreationPort.DatabaseCreationResult.Success(tempFile);
|
||||||
|
};
|
||||||
|
AtomicReference<Path> contextOverride = new AtomicReference<>();
|
||||||
|
DefaultCreateNewDatabaseUseCase useCase = new DefaultCreateNewDatabaseUseCase(
|
||||||
|
creation, trackingActiveContext(contextOverride), () -> null);
|
||||||
|
|
||||||
|
CreateNewDatabaseResult result = useCase.createNewDatabase(target);
|
||||||
|
|
||||||
|
assertThat(result).isInstanceOf(CreateNewDatabaseResult.Success.class);
|
||||||
|
assertThat(Files.exists(target)).isTrue();
|
||||||
|
assertThat(Files.readString(target)).isEqualTo("migrated-content");
|
||||||
|
assertThat(capturedTemp.get()).isNotNull();
|
||||||
|
assertThat(Files.exists(capturedTemp.get())).isFalse();
|
||||||
|
assertThat(contextOverride.get()).isEqualTo(target.toAbsolutePath().normalize());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createNewDatabase_shouldOverwriteExistingTarget_whenNoActiveDatabase(@TempDir Path tempDir)
|
||||||
|
throws IOException {
|
||||||
|
// Kein aktiver DB-Kontext (Supplier liefert null), aber Zieldatei existiert bereits →
|
||||||
|
// Überschreiben ist erlaubt, weil keine in Benutzung befindliche DB betroffen ist.
|
||||||
|
Path target = tempDir.resolve("existing.sqlite");
|
||||||
|
Files.writeString(target, "alter-inhalt");
|
||||||
|
|
||||||
|
DatabaseCreationPort creation = tempFile -> {
|
||||||
|
try {
|
||||||
|
Files.writeString(tempFile, "neu-und-leer");
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
return new DatabaseCreationPort.DatabaseCreationResult.Success(tempFile);
|
||||||
|
};
|
||||||
|
AtomicReference<Path> contextOverride = new AtomicReference<>();
|
||||||
|
DefaultCreateNewDatabaseUseCase useCase = new DefaultCreateNewDatabaseUseCase(
|
||||||
|
creation, trackingActiveContext(contextOverride), () -> null);
|
||||||
|
|
||||||
|
CreateNewDatabaseResult result = useCase.createNewDatabase(target);
|
||||||
|
|
||||||
|
assertThat(result).isInstanceOf(CreateNewDatabaseResult.Success.class);
|
||||||
|
assertThat(Files.readString(target)).isEqualTo("neu-und-leer");
|
||||||
|
assertThat(contextOverride.get()).isEqualTo(target.toAbsolutePath().normalize());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void isSameFile_shouldReturnTrue_forIdenticalNonExistingFiles(@TempDir Path tempDir) throws IOException {
|
void isSameFile_shouldReturnTrue_forIdenticalNonExistingFiles(@TempDir Path tempDir) throws IOException {
|
||||||
Path a = tempDir.resolve("data.sqlite");
|
Path a = tempDir.resolve("data.sqlite");
|
||||||
|
|||||||
+9
-5
@@ -2042,6 +2042,9 @@ public class BootstrapRunner {
|
|||||||
* {@link #activeDatabaseContext}. Die zugrundeliegende Liefer-Lambda für den aktiven
|
* {@link #activeDatabaseContext}. Die zugrundeliegende Liefer-Lambda für den aktiven
|
||||||
* DB-Pfad bevorzugt das Override im Kontext-Port; ist keines gesetzt, fällt sie auf den
|
* DB-Pfad bevorzugt das Override im Kontext-Port; ist keines gesetzt, fällt sie auf den
|
||||||
* {@code sqliteFile}-Wert der aus {@code configFilePath} geladenen Konfiguration zurück.
|
* {@code sqliteFile}-Wert der aus {@code configFilePath} geladenen Konfiguration zurück.
|
||||||
|
* Existiert weder ein Override noch eine ladbare Konfiguration, gibt es keine aktive
|
||||||
|
* Datenbank: die Lambda liefert dann {@code null}, wodurch der Use-Case die
|
||||||
|
* Selbst-Überschreib-Prüfung überspringt und die Anlage zulässt.
|
||||||
* <p>
|
* <p>
|
||||||
* Pfad-Validierungs- und Move-Schritte verbleiben im Use-Case; der Adapter führt
|
* Pfad-Validierungs- und Move-Schritte verbleiben im Use-Case; der Adapter führt
|
||||||
* Anlage, Migration und Verbindungstest aus.
|
* Anlage, Migration und Verbindungstest aus.
|
||||||
@@ -2061,9 +2064,10 @@ public class BootstrapRunner {
|
|||||||
return override.get();
|
return override.get();
|
||||||
}
|
}
|
||||||
if (configFilePath == null || !Files.exists(configFilePath)) {
|
if (configFilePath == null || !Files.exists(configFilePath)) {
|
||||||
// Ohne Konfiguration und ohne Override: Zielpfad selbst als „aktiv" werten,
|
// Ohne Override und ohne geladene Konfiguration existiert keine aktive
|
||||||
// damit die Pfadprüfung nur das Selbst-Überschreiben verhindert.
|
// Datenbank. null signalisiert dem Use-Case: keine Selbst-Überschreib-Prüfung
|
||||||
return targetFile.toAbsolutePath().normalize();
|
// nötig, die Anlage darf fortfahren.
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
migrateConfigurationIfNeeded(configFilePath);
|
migrateConfigurationIfNeeded(configFilePath);
|
||||||
@@ -2071,9 +2075,9 @@ public class BootstrapRunner {
|
|||||||
return config.sqliteFile().toAbsolutePath().normalize();
|
return config.sqliteFile().toAbsolutePath().normalize();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.warn("GUI-Datenbankanlage: aktive DB konnte nicht aus {} ermittelt werden: {} — "
|
LOG.warn("GUI-Datenbankanlage: aktive DB konnte nicht aus {} ermittelt werden: {} — "
|
||||||
+ "Zielpfad wird stattdessen als aktiv angenommen.",
|
+ "es wird keine aktive DB angenommen.",
|
||||||
configFilePath, e.getMessage());
|
configFilePath, e.getMessage());
|
||||||
return targetFile.toAbsolutePath().normalize();
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
DefaultCreateNewDatabaseUseCase useCase = new DefaultCreateNewDatabaseUseCase(
|
DefaultCreateNewDatabaseUseCase useCase = new DefaultCreateNewDatabaseUseCase(
|
||||||
|
|||||||
Reference in New Issue
Block a user