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:
+30
-23
@@ -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...)}
|
||||
* normalisiert verglichen. Für noch nicht existierende Dateien wird das Elternverzeichnis
|
||||
* 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 {
|
||||
|
||||
@@ -66,7 +68,9 @@ public class DefaultCreateNewDatabaseUseCase implements CreateNewDatabaseUseCase
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
@@ -95,6 +99,9 @@ public class DefaultCreateNewDatabaseUseCase implements CreateNewDatabaseUseCase
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return strukturiertes Ergebnis mit Erfolg oder klassifiziertem Fehler; nie {@code null}
|
||||
@@ -106,29 +113,29 @@ public class DefaultCreateNewDatabaseUseCase implements CreateNewDatabaseUseCase
|
||||
LOG.info("Neue Datenbank anlegen: angeforderter Zielpfad = {}", absoluteTarget);
|
||||
|
||||
// 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();
|
||||
if (activeDb == null) {
|
||||
LOG.error("Aktiver Datenbankpfad ist nicht ermittelbar – Anlage abgebrochen.");
|
||||
return new CreateNewDatabaseResult.CreationFailed(
|
||||
CreateNewDatabaseResult.Phase.PATH_RESOLUTION,
|
||||
"Aktiver Datenbankpfad konnte nicht ermittelt werden.",
|
||||
null);
|
||||
}
|
||||
Path absoluteActive = activeDb.toAbsolutePath().normalize();
|
||||
boolean sameFile;
|
||||
try {
|
||||
sameFile = isSameFile(absoluteActive, absoluteTarget);
|
||||
} catch (IOException e) {
|
||||
LOG.error("Pfad-Sicherheitsprüfung fehlgeschlagen: {}", e.getMessage(), e);
|
||||
return new CreateNewDatabaseResult.CreationFailed(
|
||||
CreateNewDatabaseResult.Phase.PATH_RESOLUTION,
|
||||
"Pfad-Sicherheitsprüfung fehlgeschlagen: " + e.getMessage(),
|
||||
e);
|
||||
}
|
||||
if (sameFile) {
|
||||
LOG.warn("Anlage abgelehnt: Zielpfad entspricht der aktuell aktiven Datenbank: {}",
|
||||
absoluteTarget);
|
||||
return new CreateNewDatabaseResult.SameAsActiveDatabase(absoluteTarget);
|
||||
LOG.info("Keine aktive Datenbank in Betrieb – Selbst-Überschreib-Prüfung entfällt, "
|
||||
+ "Anlage fährt fort.");
|
||||
} else {
|
||||
Path absoluteActive = activeDb.toAbsolutePath().normalize();
|
||||
boolean sameFile;
|
||||
try {
|
||||
sameFile = isSameFile(absoluteActive, absoluteTarget);
|
||||
} catch (IOException e) {
|
||||
LOG.error("Pfad-Sicherheitsprüfung fehlgeschlagen: {}", e.getMessage(), e);
|
||||
return new CreateNewDatabaseResult.CreationFailed(
|
||||
CreateNewDatabaseResult.Phase.PATH_RESOLUTION,
|
||||
"Pfad-Sicherheitsprüfung fehlgeschlagen: " + e.getMessage(),
|
||||
e);
|
||||
}
|
||||
if (sameFile) {
|
||||
LOG.warn("Anlage abgelehnt: Zielpfad entspricht der aktuell aktiven Datenbank: {}",
|
||||
absoluteTarget);
|
||||
return new CreateNewDatabaseResult.SameAsActiveDatabase(absoluteTarget);
|
||||
}
|
||||
}
|
||||
|
||||
// Schritt 2: Temp-Datei im Zielverzeichnis vorbereiten
|
||||
|
||||
+57
@@ -168,6 +168,63 @@ class DefaultCreateNewDatabaseUseCaseTest {
|
||||
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
|
||||
void isSameFile_shouldReturnTrue_forIdenticalNonExistingFiles(@TempDir Path tempDir) throws IOException {
|
||||
Path a = tempDir.resolve("data.sqlite");
|
||||
|
||||
Reference in New Issue
Block a user