Lege neue leere SQLite-Datenbank atomar via Use-Case und GUI an

Neuer Menüpunkt „Datenbank → Neue Datenbank anlegen…" mit FileChooser,
normalisierter Pfadprüfung gegen die aktive DB, gesammelter Überschreib-
Bestätigung, DB-Busy-Sperre auf Verlauf-Tab, Flyway-Migration auf den
neuesten Stand gegen eine Temp-Datei, Verbindungstest, atomarem Move
(ATOMIC_MOVE + REPLACE_EXISTING) und Umstellen der aktiven DB-Referenz
über einen neuen ActiveDatabaseContextPort. Konfig-Tab wechselt nach
Wechsel automatisch in den Dirty-State; Hinweismeldung mit Speichern-
Aufforderung wird im zentralen Meldungsbereich angezeigt.

Architektur entspricht Fall B aus der Spezifikation: Bootstrap hält den
Override prozessweit und verwendet ihn in resolveActiveJdbcUrl statt
des Werts aus der .properties-Datei. Bei Fehlern wird die Temp-Datei
zuverlässig entfernt; die aktive DB bleibt unverändert in Betrieb.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-05 16:52:54 +02:00
parent 90d95b9ff8
commit 3876e647b2
15 changed files with 1793 additions and 20 deletions
@@ -22,6 +22,7 @@ import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiAdapter;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiConfigurationFileLoader;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiConfigurationFileWriter;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiConfigurationLoadException;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiCreateNewDatabasePort;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiPromptEditorPort;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiStartupContext;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiBatchRunLaunchOutcome;
@@ -46,6 +47,7 @@ import de.gecheckt.pdf.umbenenner.adapter.out.pathcheck.FilesystemPathCheckAdapt
import de.gecheckt.pdf.umbenenner.adapter.out.pdfextraction.PdfTextExtractionPortAdapter;
import de.gecheckt.pdf.umbenenner.adapter.out.prompt.FilesystemPromptPortAdapter;
import de.gecheckt.pdf.umbenenner.adapter.out.sourcedocument.SourceDocumentCandidatesPortAdapter;
import de.gecheckt.pdf.umbenenner.adapter.out.sqlite.SqliteDatabaseCreationAdapter;
import de.gecheckt.pdf.umbenenner.adapter.out.sqlite.SqliteDocumentRecordRepositoryAdapter;
import de.gecheckt.pdf.umbenenner.adapter.out.sqlite.SqliteProcessingAttemptRepositoryAdapter;
import de.gecheckt.pdf.umbenenner.adapter.out.sqlite.SqliteSchemaInitializationAdapter;
@@ -59,6 +61,7 @@ import de.gecheckt.pdf.umbenenner.application.config.provider.ProviderConfigurat
import de.gecheckt.pdf.umbenenner.application.config.startup.StartConfiguration;
import de.gecheckt.pdf.umbenenner.application.port.in.BatchRunOutcome;
import de.gecheckt.pdf.umbenenner.application.port.in.BatchRunProcessingUseCase;
import de.gecheckt.pdf.umbenenner.application.port.in.CreateNewDatabaseUseCase;
import de.gecheckt.pdf.umbenenner.application.port.in.HistoricalDocumentContext;
import de.gecheckt.pdf.umbenenner.application.port.in.ManualFileCopyRequest;
import de.gecheckt.pdf.umbenenner.application.port.in.ManualFileCopyResult;
@@ -68,9 +71,11 @@ import de.gecheckt.pdf.umbenenner.application.port.in.ManualFileRenameResult;
import de.gecheckt.pdf.umbenenner.application.port.in.ManualFileRenameUseCase;
import de.gecheckt.pdf.umbenenner.application.port.in.ResetDocumentStatusResult;
import de.gecheckt.pdf.umbenenner.application.port.in.ResolveHistoricalDocumentContextUseCase;
import de.gecheckt.pdf.umbenenner.application.port.out.ActiveDatabaseContextPort;
import de.gecheckt.pdf.umbenenner.application.port.out.AiContentSensitivity;
import de.gecheckt.pdf.umbenenner.application.port.out.AiInvocationPort;
import de.gecheckt.pdf.umbenenner.application.port.out.ClockPort;
import de.gecheckt.pdf.umbenenner.application.port.out.DatabaseCreationPort;
import de.gecheckt.pdf.umbenenner.application.port.out.ConfigurationPort;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecordRepository;
@@ -95,6 +100,7 @@ import de.gecheckt.pdf.umbenenner.adapter.in.gui.history.GuiHistoryResetDocument
import de.gecheckt.pdf.umbenenner.adapter.out.sqlite.SqliteHistoryQueryAdapter;
import de.gecheckt.pdf.umbenenner.application.port.out.history.HistoryQueryPort;
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultBatchRunProcessingUseCase;
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultCreateNewDatabaseUseCase;
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultDeleteDocumentHistoryUseCase;
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultHistoryDetailsUseCase;
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultHistoryOverviewUseCase;
@@ -111,6 +117,7 @@ import de.gecheckt.pdf.umbenenner.bootstrap.adapter.AiModelCatalogDispatcher;
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.SqliteActiveDatabaseContextAdapter;
import de.gecheckt.pdf.umbenenner.bootstrap.singleinstance.AnotherInstanceRunningException;
import de.gecheckt.pdf.umbenenner.bootstrap.singleinstance.SingleInstanceGuard;
import de.gecheckt.pdf.umbenenner.bootstrap.startup.StartupArguments;
@@ -207,6 +214,23 @@ public class BootstrapRunner {
private final GuiAdapterFactory guiAdapterFactory;
private final SingleInstanceGuardFactory singleInstanceGuardFactory;
/**
* Eigentümer der zur Laufzeit aktiven SQLite-Datenbankreferenz. Wird im
* GUI-Pfad genutzt, damit der Use-Case
* {@link DefaultCreateNewDatabaseUseCase} die aktive Datenbank umstellen kann,
* ohne die {@code .properties}-Datei sofort schreiben zu müssen.
* <p>
* Solange kein Override gesetzt ist, verhalten sich alle DB-Adapter wie
* bisher (Pfad aus der jeweils geladenen {@link StartConfiguration}).
*/
private final SqliteActiveDatabaseContextAdapter activeDatabaseContext = new SqliteActiveDatabaseContextAdapter();
/**
* Adapter zum Anlegen einer neuen, leeren SQLite-Datenbank. Zustandslos und
* threadsicher; wird im GUI-Pfad pro Anlage-Aufruf wiederverwendet.
*/
private final DatabaseCreationPort databaseCreationPort = new SqliteDatabaseCreationAdapter();
/**
* Functional interface encapsulating the legacy configuration migration step.
* <p>
@@ -416,7 +440,7 @@ public class BootstrapRunner {
ProviderConfiguration providerConfig = startConfig.multiProviderConfiguration().activeProviderConfiguration();
AiInvocationPort aiInvocationPort = new AiProviderSelector().select(activeFamily, providerConfig);
String jdbcUrl = buildJdbcUrl(startConfig);
String jdbcUrl = resolveActiveJdbcUrl(startConfig);
FingerprintPort fingerprintPort = new Sha256FingerprintAdapter();
DocumentRecordRepository documentRecordRepository =
new SqliteDocumentRecordRepositoryAdapter(jdbcUrl);
@@ -823,6 +847,7 @@ public class BootstrapRunner {
GuiHistoricalDocumentContextPort historicalDocumentContextPort = this::resolveHistoricalDocumentContextForGui;
GuiHistoryOverviewPort historyOverviewPort = this::loadHistoryOverviewForGui;
GuiHistoryDetailsPort historyDetailsPort = this::loadHistoryDetailsForGui;
GuiCreateNewDatabasePort createNewDatabasePort = this::createNewDatabaseForGui;
GuiHistoryResetDocumentStatusPort historyResetPort = this::resetHistoryDocumentStatusForGui;
GuiDeleteDocumentHistoryPort deleteHistoryPort = this::deleteDocumentHistoryForGui;
// Versionsnummer aus dem MANIFEST.MF des gepackten JARs lesen; Fallback "dev" bei IDE-Start
@@ -852,7 +877,8 @@ public class BootstrapRunner {
historyDetailsPort,
historyResetPort,
deleteHistoryPort,
this::buildGuiPromptEditorPort);
this::buildGuiPromptEditorPort,
createNewDatabasePort);
}
Path configPath = Paths.get(configPathOverride.get());
@@ -883,7 +909,8 @@ public class BootstrapRunner {
historyDetailsPort,
historyResetPort,
deleteHistoryPort,
this::buildGuiPromptEditorPort);
this::buildGuiPromptEditorPort,
createNewDatabasePort);
}
LOG.info("GUI startup: configuration file confirmed at: {}", configPath.toAbsolutePath());
@@ -897,7 +924,7 @@ public class BootstrapRunner {
miniRunLauncher, resetPort, manualRenamePort, manualCopyPort,
historicalDocumentContextPort, applicationVersion, promptEditorPort,
historyOverviewPort, historyDetailsPort, historyResetPort, deleteHistoryPort,
this::buildGuiPromptEditorPort);
this::buildGuiPromptEditorPort, createNewDatabasePort);
} catch (GuiConfigurationLoadException e) {
LOG.error("GUI startup: configuration could not be loaded, starting without it: {}",
e.getMessage(), e);
@@ -924,7 +951,8 @@ public class BootstrapRunner {
historyDetailsPort,
historyResetPort,
deleteHistoryPort,
this::buildGuiPromptEditorPort);
this::buildGuiPromptEditorPort,
createNewDatabasePort);
}
}
@@ -1171,7 +1199,7 @@ public class BootstrapRunner {
StartConfiguration config = loadAndValidateConfiguration(configFilePath);
initializeSchema(config);
String jdbcUrl = buildJdbcUrl(config);
String jdbcUrl = resolveActiveJdbcUrl(config);
UnitOfWorkPort unitOfWorkPort = new SqliteUnitOfWorkAdapter(jdbcUrl);
ProcessingLogger resetLogger = new Log4jProcessingLogger(
DefaultResetDocumentStatusUseCase.class,
@@ -1229,7 +1257,7 @@ public class BootstrapRunner {
*/
private ManualFileRenameUseCase buildProductionManualFileRenameUseCase(
StartConfiguration startConfig) {
String jdbcUrl = buildJdbcUrl(startConfig);
String jdbcUrl = resolveActiveJdbcUrl(startConfig);
DocumentRecordRepository documentRecordRepository =
new SqliteDocumentRecordRepositoryAdapter(jdbcUrl);
UnitOfWorkPort unitOfWorkPort = new SqliteUnitOfWorkAdapter(jdbcUrl);
@@ -1263,7 +1291,7 @@ public class BootstrapRunner {
*/
private ManualFileCopyUseCase buildProductionManualFileCopyUseCase(
StartConfiguration startConfig) {
String jdbcUrl = buildJdbcUrl(startConfig);
String jdbcUrl = resolveActiveJdbcUrl(startConfig);
DocumentRecordRepository documentRecordRepository =
new SqliteDocumentRecordRepositoryAdapter(jdbcUrl);
UnitOfWorkPort unitOfWorkPort = new SqliteUnitOfWorkAdapter(jdbcUrl);
@@ -1442,7 +1470,7 @@ public class BootstrapRunner {
migrateConfigurationIfNeeded(configFilePath);
StartConfiguration config = loadAndValidateConfiguration(configFilePath);
initializeSchema(config);
String jdbcUrl = buildJdbcUrl(config);
String jdbcUrl = resolveActiveJdbcUrl(config);
DocumentRecordRepository documentRecordRepository =
new SqliteDocumentRecordRepositoryAdapter(jdbcUrl);
ResolveHistoricalDocumentContextUseCase useCase =
@@ -1475,7 +1503,7 @@ public class BootstrapRunner {
migrateConfigurationIfNeeded(configFilePath);
StartConfiguration config = loadAndValidateConfiguration(configFilePath);
initializeSchema(config);
String jdbcUrl = buildJdbcUrl(config);
String jdbcUrl = resolveActiveJdbcUrl(config);
HistoryQueryPort historyQueryPort = new SqliteHistoryQueryAdapter(jdbcUrl);
DefaultHistoryOverviewUseCase useCase = new DefaultHistoryOverviewUseCase(historyQueryPort);
return useCase.loadOverview(query);
@@ -1505,7 +1533,7 @@ public class BootstrapRunner {
migrateConfigurationIfNeeded(configFilePath);
StartConfiguration config = loadAndValidateConfiguration(configFilePath);
initializeSchema(config);
String jdbcUrl = buildJdbcUrl(config);
String jdbcUrl = resolveActiveJdbcUrl(config);
HistoryQueryPort historyQueryPort = new SqliteHistoryQueryAdapter(jdbcUrl);
DefaultHistoryDetailsUseCase useCase = new DefaultHistoryDetailsUseCase(historyQueryPort);
return useCase.loadDetails(fingerprint);
@@ -1536,7 +1564,7 @@ public class BootstrapRunner {
migrateConfigurationIfNeeded(configFilePath);
StartConfiguration config = loadAndValidateConfiguration(configFilePath);
initializeSchema(config);
String jdbcUrl = buildJdbcUrl(config);
String jdbcUrl = resolveActiveJdbcUrl(config);
UnitOfWorkPort unitOfWorkPort = new SqliteUnitOfWorkAdapter(jdbcUrl);
DefaultHistoryResetDocumentStatusUseCase useCase =
new DefaultHistoryResetDocumentStatusUseCase(unitOfWorkPort);
@@ -1568,7 +1596,7 @@ public class BootstrapRunner {
migrateConfigurationIfNeeded(configFilePath);
StartConfiguration config = loadAndValidateConfiguration(configFilePath);
initializeSchema(config);
String jdbcUrl = buildJdbcUrl(config);
String jdbcUrl = resolveActiveJdbcUrl(config);
UnitOfWorkPort unitOfWorkPort = new SqliteUnitOfWorkAdapter(jdbcUrl);
DefaultDeleteDocumentHistoryUseCase useCase =
new DefaultDeleteDocumentHistoryUseCase(unitOfWorkPort);
@@ -1582,6 +1610,53 @@ public class BootstrapRunner {
}
}
/**
* Bridge-Methode zum Anlegen einer neuen, leeren SQLite-Datenbank für die GUI.
* <p>
* Verdrahtet pro Aufruf einen frischen {@link DefaultCreateNewDatabaseUseCase} mit dem
* gemeinsam gehaltenen {@link #databaseCreationPort} und dem
* {@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
* {@code sqliteFile}-Wert der aus {@code configFilePath} geladenen Konfiguration zurück.
* <p>
* Pfad-Validierungs- und Move-Schritte verbleiben im Use-Case; der Adapter führt
* Anlage, Migration und Verbindungstest aus.
*
* @param configFilePath der aktuell in der GUI bekannte Konfigurationspfad oder
* {@code null}, wenn noch keine Konfiguration geladen wurde
* @param targetFile der vom Benutzer gewählte Zielpfad; darf nicht {@code null} sein
* @return strukturiertes Ergebnis des Use-Cases; nie {@code null}
*/
CreateNewDatabaseUseCase.CreateNewDatabaseResult createNewDatabaseForGui(
Path configFilePath, Path targetFile) {
Objects.requireNonNull(targetFile, "targetFile must not be null");
LOG.info("GUI-Datenbankanlage: angeforderter Zielpfad = {}", targetFile);
DefaultCreateNewDatabaseUseCase.ActiveDatabasePathSupplier activeDbSupplier = () -> {
Optional<Path> override = activeDatabaseContext.activeDatabaseOverride();
if (override.isPresent()) {
return override.get();
}
if (configFilePath == null || !Files.exists(configFilePath)) {
// Ohne Konfiguration und ohne Override: Zielpfad selbst als „aktiv" werten,
// damit die Pfadprüfung nur das Selbst-Überschreiben verhindert.
return targetFile.toAbsolutePath().normalize();
}
try {
migrateConfigurationIfNeeded(configFilePath);
StartConfiguration config = loadAndValidateConfiguration(configFilePath);
return config.sqliteFile().toAbsolutePath().normalize();
} catch (Exception e) {
LOG.warn("GUI-Datenbankanlage: aktive DB konnte nicht aus {} ermittelt werden: {} — "
+ "Zielpfad wird stattdessen als aktiv angenommen.",
configFilePath, e.getMessage());
return targetFile.toAbsolutePath().normalize();
}
};
DefaultCreateNewDatabaseUseCase useCase = new DefaultCreateNewDatabaseUseCase(
databaseCreationPort, activeDatabaseContext, activeDbSupplier);
return useCase.createNewDatabase(targetFile);
}
/**
* Builds a {@link ResetDocumentStatusResult} where every requested fingerprint is
* recorded as a failure with the given error message.
@@ -1724,7 +1799,7 @@ public class BootstrapRunner {
* @param config the validated startup configuration containing the SQLite file path
*/
private void initializeSchema(StartConfiguration config) {
schemaInitPortFactory.create(buildJdbcUrl(config)).initializeSchema();
schemaInitPortFactory.create(resolveActiveJdbcUrl(config)).initializeSchema();
}
/**
@@ -1870,11 +1945,48 @@ public class BootstrapRunner {
/**
* Builds the JDBC URL for the SQLite database from the configured file path.
* <p>
* Diese Variante berücksichtigt {@em kein} Override sie wird ausschließlich für
* den Headless-Pfad und für GUI-Tests aufgerufen, in denen kein
* {@link ActiveDatabaseContextPort} verfügbar ist.
*
* @param config the startup configuration containing the SQLite file path
* @return the JDBC URL in the form {@code jdbc:sqlite:/path/to/file.db}
*/
static String buildJdbcUrl(StartConfiguration config) {
return "jdbc:sqlite:" + config.sqliteFile().toAbsolutePath().toString().replace('\\', '/');
return buildSqliteJdbcUrl(config.sqliteFile());
}
/**
* Liefert die JDBC-URL der zur Laufzeit aktiven SQLite-Datenbank.
* <p>
* Hat der {@link ActiveDatabaseContextPort} einen Override gesetzt (z. B. nachdem
* der Benutzer im GUI eine neue Datenbank angelegt hat), wird dieser verwendet.
* Andernfalls greift der Wert aus der übergebenen {@link StartConfiguration}.
*
* @param config die geladene Startup-Konfiguration; nie {@code null}
* @return die JDBC-URL der aktiven SQLite-Datei
*/
String resolveActiveJdbcUrl(StartConfiguration config) {
Optional<Path> override = activeDatabaseContext.activeDatabaseOverride();
if (override.isPresent()) {
return buildSqliteJdbcUrl(override.get());
}
return buildSqliteJdbcUrl(config.sqliteFile());
}
/**
* Liefert den Pfad der zur Laufzeit aktiven SQLite-Datei.
*
* @param config die geladene Startup-Konfiguration; nie {@code null}
* @return der absolute Pfad der aktuell aktiven Datenbankdatei
*/
Path resolveActiveDatabasePath(StartConfiguration config) {
return activeDatabaseContext.activeDatabaseOverride()
.orElseGet(() -> config.sqliteFile().toAbsolutePath().normalize());
}
private static String buildSqliteJdbcUrl(Path sqliteFile) {
return "jdbc:sqlite:" + sqliteFile.toAbsolutePath().toString().replace('\\', '/');
}
}
@@ -0,0 +1,92 @@
package de.gecheckt.pdf.umbenenner.bootstrap.adapter;
import java.nio.file.Path;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.gecheckt.pdf.umbenenner.application.port.out.ActiveDatabaseContextPort;
/**
* Bootstrap-interne Implementierung des {@link ActiveDatabaseContextPort}.
* <p>
* Hält einen prozessweiten, threadsicheren Override-Pfad, der in der Bootstrap-Schicht
* Vorrang vor dem in der {@code .properties}-Datei konfigurierten SQLite-Pfad hat.
* Solange kein Override gesetzt ist, verhält sich die Anwendung wie bisher: alle
* SQLite-Adapter werden mit der aus der jeweils geladenen Konfigurationsdatei
* abgeleiteten JDBC-URL verdrahtet.
* <p>
* Setzt der Use-Case
* {@link de.gecheckt.pdf.umbenenner.application.usecase.DefaultCreateNewDatabaseUseCase}
* den Override über {@link #switchActiveDatabase(Path)}, verwenden alle nachfolgenden
* GUI-DB-Operationen (History, Reset, Löschen, Verarbeitungsläufe) die hinterlegte
* Datei auch wenn der Benutzer die Konfigurationsdatei noch nicht gespeichert hat.
*
* <h2>Lebensdauer</h2>
* <p>Die gehaltene Referenz lebt für die Dauer des laufenden GUI-Prozesses. Sie ist
* nicht persistent: nach einem Neustart greift wieder der Wert aus der
* Konfigurationsdatei.
*
* <h2>Threading</h2>
* <p>Alle Lese- und Schreibzugriffe gehen über eine {@link AtomicReference}; die Klasse
* ist daher uneingeschränkt threadsicher.
*/
public class SqliteActiveDatabaseContextAdapter implements ActiveDatabaseContextPort {
private static final Logger LOG = LogManager.getLogger(SqliteActiveDatabaseContextAdapter.class);
private final AtomicReference<Path> override = new AtomicReference<>();
/**
* Standardkonstruktor.
*/
public SqliteActiveDatabaseContextAdapter() {
// kein Zustand außer der AtomicReference
}
/**
* Setzt den Override-Pfad auf die übergebene Datei. Nachfolgende DB-Operationen
* verwenden diese Datei, sofern sie {@link #activeDatabaseOverride()} abfragen,
* statt direkt die Konfigurationsdatei zu konsultieren.
*
* @param newDbFile absoluter Pfad der neuen aktiven Datenbankdatei; darf nicht
* {@code null} sein
* @throws NullPointerException wenn {@code newDbFile} {@code null} ist
*/
@Override
public void switchActiveDatabase(Path newDbFile) {
Objects.requireNonNull(newDbFile, "newDbFile darf nicht null sein");
Path absolute = newDbFile.toAbsolutePath().normalize();
Path previous = override.getAndSet(absolute);
if (previous == null) {
LOG.info("Aktive SQLite-Datenbank wurde umgestellt auf: {}", absolute);
} else {
LOG.info("Aktive SQLite-Datenbank wurde von {} auf {} umgestellt.",
previous, absolute);
}
}
/**
* Liefert den aktuell gesetzten Override, falls vorhanden.
*
* @return der Override-Pfad oder {@link Optional#empty()}
*/
@Override
public Optional<Path> activeDatabaseOverride() {
return Optional.ofNullable(override.get());
}
/**
* Entfernt den Override (für Tests oder kontrolliertes Zurücksetzen). Nach dem
* Aufruf greift wieder der Wert aus der konfigurierten {@code .properties}-Datei.
*/
public void clearOverride() {
Path previous = override.getAndSet(null);
if (previous != null) {
LOG.info("Aktiver SQLite-Datenbank-Override wurde entfernt (zuletzt: {}).", previous);
}
}
}
@@ -0,0 +1,65 @@
package de.gecheckt.pdf.umbenenner.bootstrap.adapter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.nio.file.Path;
import java.util.Optional;
import org.junit.jupiter.api.Test;
/**
* Tests für {@link SqliteActiveDatabaseContextAdapter}.
* <p>
* Prüft das Setzen, Auslesen und Zurücksetzen des Override-Pfads sowie die Pflicht-Prüfung
* gegen {@code null}-Argumente.
*/
class SqliteActiveDatabaseContextAdapterTest {
@Test
void activeDatabaseOverride_returnsEmpty_initially() {
SqliteActiveDatabaseContextAdapter adapter = new SqliteActiveDatabaseContextAdapter();
assertThat(adapter.activeDatabaseOverride()).isEmpty();
}
@Test
void switchActiveDatabase_setsAbsoluteOverride() {
SqliteActiveDatabaseContextAdapter adapter = new SqliteActiveDatabaseContextAdapter();
Path target = Path.of("data/foo.sqlite");
adapter.switchActiveDatabase(target);
Optional<Path> override = adapter.activeDatabaseOverride();
assertThat(override).isPresent();
assertThat(override.get()).isEqualTo(target.toAbsolutePath().normalize());
}
@Test
void switchActiveDatabase_replacesPreviousOverride() {
SqliteActiveDatabaseContextAdapter adapter = new SqliteActiveDatabaseContextAdapter();
adapter.switchActiveDatabase(Path.of("first.sqlite"));
adapter.switchActiveDatabase(Path.of("second.sqlite"));
assertThat(adapter.activeDatabaseOverride())
.isPresent()
.get()
.isEqualTo(Path.of("second.sqlite").toAbsolutePath().normalize());
}
@Test
void clearOverride_removesPreviouslySetOverride() {
SqliteActiveDatabaseContextAdapter adapter = new SqliteActiveDatabaseContextAdapter();
adapter.switchActiveDatabase(Path.of("foo.sqlite"));
adapter.clearOverride();
assertThat(adapter.activeDatabaseOverride()).isEmpty();
}
@Test
void switchActiveDatabase_rejectsNull() {
SqliteActiveDatabaseContextAdapter adapter = new SqliteActiveDatabaseContextAdapter();
assertThatThrownBy(() -> adapter.switchActiveDatabase(null))
.isInstanceOf(NullPointerException.class);
}
}