Erhoehe new-code-Coverage durch gezielte Tests fuer Reliability-Fixes
Schliesst die durch die SonarQube-Reliability-Fixes (Commit 32e32a9)
neu eingefuehrten Quellzeilen testseitig ab, damit das new-coverage
Quality Gate von 80% wieder erreicht wird.
PdfPreviewPaneRenderingTest:
- erzeugt mit PDFBox echte ein- bzw. mehrseitige PDFs und ruft
loadSource() auf, sodass loadAndRenderFirstPageOnWorker (inkl.
AtomicReference-Setter fuer currentDocument/currentRenderer) und
renderPageOnWorker (AtomicReference-Getter) tatsaechlich ausgefuehrt
werden.
BootstrapRunnerGuiContextInitFailureTest:
- deckt die catch-Zweige fuer InvalidStartConfigurationException,
DocumentPersistenceException und unspezifische RuntimeException in
initializeApplicationRunContext ab. Damit werden die Pfade ausgefuehrt,
in denen guiApplicationRunContext via AtomicReference.set(Optional.empty())
zurueckgesetzt wird.
Keine Aenderungen an Produktivcode oder bestehenden Tests.
This commit is contained in:
+196
@@ -0,0 +1,196 @@
|
||||
package de.gecheckt.pdf.umbenenner.bootstrap;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiAdapter;
|
||||
import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiStartupContext;
|
||||
import de.gecheckt.pdf.umbenenner.adapter.out.bootstrap.validation.InvalidStartConfigurationException;
|
||||
import de.gecheckt.pdf.umbenenner.adapter.out.bootstrap.validation.StartConfigurationValidator;
|
||||
import de.gecheckt.pdf.umbenenner.application.config.provider.AiProviderFamily;
|
||||
import de.gecheckt.pdf.umbenenner.application.config.provider.MultiProviderConfiguration;
|
||||
import de.gecheckt.pdf.umbenenner.application.config.provider.ProviderConfiguration;
|
||||
import de.gecheckt.pdf.umbenenner.application.config.startup.StartConfiguration;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.ConfigurationPort;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException;
|
||||
import de.gecheckt.pdf.umbenenner.bootstrap.startup.StartupArguments;
|
||||
import de.gecheckt.pdf.umbenenner.bootstrap.startup.StartupMode;
|
||||
|
||||
/**
|
||||
* Tests, die die einzelnen {@code catch}-Zweige von
|
||||
* {@code BootstrapRunner.initializeApplicationRunContext()} abdecken.
|
||||
* <p>
|
||||
* Der Pfad für {@code ConfigurationLoadingException} ist bereits durch
|
||||
* {@link BootstrapRunnerConfigPathSemanticsTest} abgedeckt; dieser Test
|
||||
* fokussiert auf die verbleibenden Fehlerklassen
|
||||
* ({@link InvalidStartConfigurationException},
|
||||
* {@link DocumentPersistenceException} und {@link RuntimeException}),
|
||||
* damit auch deren Zustandsübergänge auf
|
||||
* {@code guiApplicationRunContext} ausgeführt werden.
|
||||
*/
|
||||
class BootstrapRunnerGuiContextInitFailureTest {
|
||||
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
|
||||
@Test
|
||||
void guiStart_validatorThrowsInvalidStartConfiguration_contextErrorIsForwarded() throws Exception {
|
||||
Path configFile = tempDir.resolve("invalid-validation.properties");
|
||||
Files.createFile(configFile);
|
||||
|
||||
AtomicReference<GuiStartupContext> capturedContext = new AtomicReference<>();
|
||||
BootstrapRunner runner = guiRunner(
|
||||
buildValidConfigPort(),
|
||||
() -> new StartConfigurationValidator() {
|
||||
@Override
|
||||
public void validate(StartConfiguration config) {
|
||||
throw new InvalidStartConfigurationException("Validierung fehlgeschlagen (Test)");
|
||||
}
|
||||
},
|
||||
jdbcUrl -> () -> { /* no-op schema init */ },
|
||||
ctx -> capturedContext.set(ctx));
|
||||
|
||||
int exitCode = runner.run(new StartupArguments(StartupMode.GUI,
|
||||
Optional.of(configFile.toString())));
|
||||
|
||||
assertEquals(0, exitCode, "GUI-Modus muss bei Validierungsfehler trotzdem regulär enden");
|
||||
GuiStartupContext context = capturedContext.get();
|
||||
assertNotNull(context, "GuiStartupContext muss an den GuiAdapter übergeben werden");
|
||||
assertTrue(context.applicationContextError().isPresent(),
|
||||
"applicationContextError muss bei Validierungsfehler gesetzt sein");
|
||||
assertTrue(context.applicationContextError().get().contains("Konfiguration nicht lauffähig"),
|
||||
"Fehlermeldung muss den Validierungspräfix enthalten");
|
||||
}
|
||||
|
||||
@Test
|
||||
void guiStart_schemaInitThrowsDocumentPersistence_contextErrorIsForwarded() throws Exception {
|
||||
Path configFile = tempDir.resolve("invalid-schema.properties");
|
||||
Files.createFile(configFile);
|
||||
|
||||
AtomicReference<GuiStartupContext> capturedContext = new AtomicReference<>();
|
||||
BootstrapRunner runner = guiRunner(
|
||||
buildValidConfigPort(),
|
||||
() -> new StartConfigurationValidator() {
|
||||
@Override
|
||||
public void validate(StartConfiguration config) { /* no-op valid validator */ }
|
||||
},
|
||||
jdbcUrl -> () -> {
|
||||
throw new DocumentPersistenceException("Schema-Init fehlgeschlagen (Test)");
|
||||
},
|
||||
ctx -> capturedContext.set(ctx));
|
||||
|
||||
int exitCode = runner.run(new StartupArguments(StartupMode.GUI,
|
||||
Optional.of(configFile.toString())));
|
||||
|
||||
assertEquals(0, exitCode, "GUI-Modus muss bei SQLite-Fehler trotzdem regulär enden");
|
||||
GuiStartupContext context = capturedContext.get();
|
||||
assertNotNull(context);
|
||||
assertTrue(context.applicationContextError().isPresent(),
|
||||
"applicationContextError muss bei SQLite-Init-Fehler gesetzt sein");
|
||||
assertTrue(context.applicationContextError().get().contains("SQLite konnte nicht initialisiert werden"),
|
||||
"Fehlermeldung muss den SQLite-Präfix enthalten");
|
||||
}
|
||||
|
||||
@Test
|
||||
void guiStart_unexpectedRuntimeException_contextErrorIsForwarded() throws Exception {
|
||||
Path configFile = tempDir.resolve("invalid-runtime.properties");
|
||||
Files.createFile(configFile);
|
||||
|
||||
AtomicReference<GuiStartupContext> capturedContext = new AtomicReference<>();
|
||||
BootstrapRunner runner = guiRunner(
|
||||
buildValidConfigPort(),
|
||||
() -> new StartConfigurationValidator() {
|
||||
@Override
|
||||
public void validate(StartConfiguration config) {
|
||||
throw new IllegalStateException("Unerwarteter Initialisierungsfehler (Test)");
|
||||
}
|
||||
},
|
||||
jdbcUrl -> () -> { /* unreachable */ },
|
||||
ctx -> capturedContext.set(ctx));
|
||||
|
||||
int exitCode = runner.run(new StartupArguments(StartupMode.GUI,
|
||||
Optional.of(configFile.toString())));
|
||||
|
||||
assertEquals(0, exitCode, "GUI-Modus muss bei unerwartetem Fehler trotzdem regulär enden");
|
||||
GuiStartupContext context = capturedContext.get();
|
||||
assertNotNull(context);
|
||||
assertTrue(context.applicationContextError().isPresent(),
|
||||
"applicationContextError muss bei unerwartetem Fehler gesetzt sein");
|
||||
assertTrue(context.applicationContextError().get()
|
||||
.contains("Unerwarteter Fehler bei der Kontextinitialisierung"),
|
||||
"Fehlermeldung muss den allgemeinen Präfix enthalten");
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private BootstrapRunner guiRunner(
|
||||
BootstrapRunner.ConfigurationPortFactory configPortFactory,
|
||||
BootstrapRunner.ValidatorFactory validatorFactory,
|
||||
BootstrapRunner.SchemaInitializationPortFactory schemaInitFactory,
|
||||
java.util.function.Consumer<GuiStartupContext> contextSink) {
|
||||
return new BootstrapRunner(
|
||||
path -> { /* no-op migration */ },
|
||||
configPortFactory,
|
||||
lockFile -> { throw new AssertionError("RunLockPort must not be called in GUI mode"); },
|
||||
validatorFactory,
|
||||
schemaInitFactory,
|
||||
(config, lock) -> { throw new AssertionError("UseCaseFactory must not be called in GUI mode"); },
|
||||
useCase -> { throw new AssertionError("CommandFactory must not be called in GUI mode"); },
|
||||
() -> new GuiAdapter() {
|
||||
@Override
|
||||
public void start(GuiStartupContext startupContext) {
|
||||
contextSink.accept(startupContext);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private BootstrapRunner.ConfigurationPortFactory buildValidConfigPort() {
|
||||
return path -> {
|
||||
try {
|
||||
Path source = tempDir.resolve("source");
|
||||
Path target = tempDir.resolve("target");
|
||||
Files.createDirectories(source);
|
||||
Files.createDirectories(target);
|
||||
Path sqliteFile = tempDir.resolve("db.sqlite");
|
||||
if (!Files.exists(sqliteFile)) {
|
||||
Files.createFile(sqliteFile);
|
||||
}
|
||||
Path promptFile = tempDir.resolve("prompt.txt");
|
||||
if (!Files.exists(promptFile)) {
|
||||
Files.createFile(promptFile);
|
||||
}
|
||||
ProviderConfiguration providerConfig = new ProviderConfiguration(
|
||||
"gpt-4", 30, "https://api.example.com", "test-key");
|
||||
MultiProviderConfiguration multiConfig = new MultiProviderConfiguration(
|
||||
AiProviderFamily.OPENAI_COMPATIBLE, providerConfig, null);
|
||||
StartConfiguration config = new StartConfiguration(
|
||||
source,
|
||||
target,
|
||||
sqliteFile,
|
||||
multiConfig,
|
||||
3, 100, 50000, 60,
|
||||
promptFile,
|
||||
tempDir.resolve("lock.lock"),
|
||||
tempDir.resolve("logs"),
|
||||
"INFO",
|
||||
false
|
||||
);
|
||||
return (ConfigurationPort) () -> config;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to build valid ConfigurationPort", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user