GUI-Bugfixes: Defaults beim Start, kopierbare Meldungen mit Zeitstempel, Befundauflistung, Modell-ComboBox links, effektiver API-Key für Modellabruf

- Blank-Startzustand zeigt jetzt die Standardvorlage (wie nach "Neu"), neue Factory createEmptyStartState für Tests
- Meldungsbereich ist per Kontextmenü bzw. Strg+C kopierbar
- Jede Meldung trägt ein führendes [HH:mm:ss]-Präfix
- Validieren- und Tests-Aktionen akkumulieren Meldungen, automatische Validierung ersetzt still ihre Einträge
- Validieren-Meldung listet alle konkreten Befunde einzeln auf
- Modell-ComboBox und manuelles Modellfeld sind linksbündig
- ApiKeyResolutionPort liefert jetzt den effektiven API-Schlüsselwert (Default + Env-Adapter-Override), so dass der Modellliste-Test in den technischen Tests nicht mehr "API-Schlüssel fehlt" meldet, obwohl er gesetzt ist
This commit is contained in:
2026-04-21 16:04:15 +02:00
parent 6babdd226e
commit ada7e203e3
18 changed files with 471 additions and 204 deletions
@@ -1,5 +1,7 @@
package de.gecheckt.pdf.umbenenner.application.validation.editor;
import java.util.Optional;
import de.gecheckt.pdf.umbenenner.application.config.provider.AiProviderFamily;
import de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor;
@@ -22,6 +24,7 @@ import de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApi
* <p>
* Implementierungen dieses Ports liegen im Adapter-Out-Modul.
*/
@FunctionalInterface
public interface ApiKeyResolutionPort {
/**
@@ -37,4 +40,32 @@ public interface ApiKeyResolutionPort {
* @return der Descriptor für die effektive Schlüsselherkunft; nie {@code null}
*/
EffectiveApiKeyDescriptor resolve(AiProviderFamily family, String propertyValue);
/**
* Liefert den effektiven API-Schlüssel-Rohwert anhand derselben Vorrangregel wie {@link #resolve}.
* <p>
* Dient technischen Tests wie dem Modellkatalogabruf, die den tatsächlichen Schlüssel im
* HTTP-Header benötigen. Die Herkunft selbst wird nicht mit zurückgegeben dafür ist
* {@link #resolve} zuständig.
* <p>
* Diese Default-Implementierung deckt den Fall ab, in dem ein Adapter ausschließlich die
* Property-Datei kennt: liefert {@link #resolve} {@code ABSENT}, wird ein leerer Optional
* zurückgegeben; andernfalls wird der nicht-leere Property-Wert geliefert. Adapter, die
* Umgebungsvariablen lesen, müssen diese Methode überschreiben, damit der ENV-Wert auch
* tatsächlich an HTTP-Aufrufer durchgereicht wird.
*
* @param family die Provider-Familie; darf nicht {@code null} sein
* @param propertyValue aktueller Property-Wert aus dem Editor (kann leer sein); darf nicht {@code null} sein
* @return der effektive Schlüssel-Rohwert, falls eine der Quellen einen Wert liefert; sonst leer
*/
default Optional<String> resolveEffectiveApiKeyValue(AiProviderFamily family, String propertyValue) {
EffectiveApiKeyDescriptor descriptor = resolve(family, propertyValue);
if (descriptor.isAbsent()) {
return Optional.empty();
}
if (propertyValue != null && !propertyValue.isBlank()) {
return Optional.of(propertyValue);
}
return Optional.empty();
}
}
@@ -28,10 +28,12 @@ import de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApi
* @param claudeModel Rohtextwert des Claude-Modellnamens
* @param claudeTimeoutSeconds Rohtextwert des Claude-Timeouts
* @param claudeApiKeyDescriptor API-Key-Herkunft für den Claude-Provider; nie {@code null}
* @param claudeApiKeyPropertyValue Rohwert des Claude-API-Schlüssels aus der Properties-Datei
* @param openaiBaseUrl Rohtextwert der OpenAI-kompatiblen Basis-URL
* @param openaiModel Rohtextwert des OpenAI-kompatiblen Modellnamens
* @param openaiTimeoutSeconds Rohtextwert des OpenAI-kompatiblen Timeouts
* @param openaiApiKeyDescriptor API-Key-Herkunft für den OpenAI-kompatiblen Provider; nie {@code null}
* @param openaiApiKeyPropertyValue Rohwert des OpenAI-API-Schlüssels aus der Properties-Datei
*/
public record EditorValidationInput(
String activeProviderIdentifier,
@@ -46,10 +48,12 @@ public record EditorValidationInput(
String claudeModel,
String claudeTimeoutSeconds,
EffectiveApiKeyDescriptor claudeApiKeyDescriptor,
String claudeApiKeyPropertyValue,
String openaiBaseUrl,
String openaiModel,
String openaiTimeoutSeconds,
EffectiveApiKeyDescriptor openaiApiKeyDescriptor) {
EffectiveApiKeyDescriptor openaiApiKeyDescriptor,
String openaiApiKeyPropertyValue) {
/**
* Erstellt eine neue Eingabe für den Validator.
@@ -66,10 +70,12 @@ public record EditorValidationInput(
* @param claudeModel Claude-Modellname; {@code null} wird zu leerem String
* @param claudeTimeoutSeconds Claude-Timeout; {@code null} wird zu leerem String
* @param claudeApiKeyDescriptor Claude-API-Key-Herkunft; darf nicht {@code null} sein
* @param claudeApiKeyPropertyValue Claude-API-Key-Rohwert aus der Properties-Datei; {@code null} wird zu leerem String
* @param openaiBaseUrl OpenAI-Basis-URL; {@code null} wird zu leerem String
* @param openaiModel OpenAI-Modellname; {@code null} wird zu leerem String
* @param openaiTimeoutSeconds OpenAI-Timeout; {@code null} wird zu leerem String
* @param openaiApiKeyDescriptor OpenAI-API-Key-Herkunft; darf nicht {@code null} sein
* @param openaiApiKeyPropertyValue OpenAI-API-Key-Rohwert aus der Properties-Datei; {@code null} wird zu leerem String
* @throws NullPointerException wenn {@code claudeApiKeyDescriptor} oder {@code openaiApiKeyDescriptor} {@code null} sind
*/
public EditorValidationInput {
@@ -86,11 +92,13 @@ public record EditorValidationInput(
claudeTimeoutSeconds = normalizeText(claudeTimeoutSeconds);
claudeApiKeyDescriptor = Objects.requireNonNull(claudeApiKeyDescriptor,
"claudeApiKeyDescriptor must not be null");
claudeApiKeyPropertyValue = normalizeText(claudeApiKeyPropertyValue);
openaiBaseUrl = normalizeText(openaiBaseUrl);
openaiModel = normalizeText(openaiModel);
openaiTimeoutSeconds = normalizeText(openaiTimeoutSeconds);
openaiApiKeyDescriptor = Objects.requireNonNull(openaiApiKeyDescriptor,
"openaiApiKeyDescriptor must not be null");
openaiApiKeyPropertyValue = normalizeText(openaiApiKeyPropertyValue);
}
private static String normalizeText(String value) {
@@ -79,6 +79,8 @@ public class ProviderTechnicalTestService {
private static final int DEFAULT_TIMEOUT_SECONDS = 30;
private final AiModelCatalogPort modelCatalogPort;
private final ApiKeyResolutionPort apiKeyResolutionPort;
/**
* Erstellt einen neuen Service mit den erforderlichen Ports.
*
@@ -89,7 +91,7 @@ public class ProviderTechnicalTestService {
public ProviderTechnicalTestService(AiModelCatalogPort modelCatalogPort,
ApiKeyResolutionPort apiKeyResolutionPort) {
this.modelCatalogPort = Objects.requireNonNull(modelCatalogPort, "modelCatalogPort must not be null");
Objects.requireNonNull(apiKeyResolutionPort,
this.apiKeyResolutionPort = Objects.requireNonNull(apiKeyResolutionPort,
"apiKeyResolutionPort must not be null");
}
@@ -377,13 +379,10 @@ public class ProviderTechnicalTestService {
/**
* Baut den {@link ModelCatalogRequest} aus dem aktuellen Editorzustand auf.
* <p>
* Da {@link EditorValidationInput} keinen direkten API-Key-String enthält, sondern
* nur einen bereits aufgelösten {@link EffectiveApiKeyDescriptor}, wird der Descriptor
* aus dem Editorzustand direkt verwendet. Der Adapter-Out-Seitige Dispatcher erwartet
* den Key entweder als ENV-Variable (die er selbst liest) oder als optionalen Wert
* im Request. Da die Auflösung beim Service bereits über {@link ApiKeyResolutionPort}
* erfolgt ist, wird für den Catalog-Request ein leerer Optional-Wert geliefert
* der Adapter verwendet dann intern seine eigene ENV-Variable-Auflösung.
* Der effektive API-Key-Rohwert wird über den {@link ApiKeyResolutionPort} ermittelt
* (Vorrangregel: providerspezifische ENV → Legacy-ENV → Property-Wert) und in den
* Request übernommen. Dadurch ist der Schlüssel bereits beim Adapter verfügbar und
* spiegelt exakt die Quelle wider, die zuvor im Deskriptor ausgewiesen wurde.
*
* @param input aktueller Editorzustand
* @param family aktive Provider-Familie
@@ -393,12 +392,9 @@ public class ProviderTechnicalTestService {
private ModelCatalogRequest buildCatalogRequest(EditorValidationInput input,
AiProviderFamily family,
EffectiveApiKeyDescriptor apiKeyDesc) {
// EditorValidationInput enthält keinen direkten API-Key-String-Wert, nur den Descriptor.
// Für den ModelCatalogRequest übergeben wir einen leeren Optional für den apiKey,
// sodass der Adapter seine eigene ENV-Variable-Auflösung durchführt.
// Der Adapter liefert dann IncompleteConfiguration, wenn auch er keinen Key findet
// was aber nicht passiert, da wir oben bereits geprüft haben, dass apiKeyDesc nicht ABSENT ist.
Optional<String> apiKeyForRequest = Optional.empty();
String propertyValue = resolveApiKeyPropertyValue(input, family);
Optional<String> apiKeyForRequest = apiKeyResolutionPort
.resolveEffectiveApiKeyValue(family, propertyValue);
String rawBaseUrl = resolveBaseUrlValue(input, family);
Optional<String> baseUrl = rawBaseUrl.isBlank() ? Optional.empty() : Optional.of(rawBaseUrl);
@@ -412,6 +408,21 @@ public class ProviderTechnicalTestService {
timeout);
}
/**
* Liest den Roh-Property-Wert des API-Schlüssels für die angegebene Provider-Familie
* aus dem Editorzustand.
*
* @param input aktueller Editorzustand
* @param family aktive Provider-Familie
* @return Property-Wert; nie {@code null}, leer wenn nicht gesetzt
*/
private String resolveApiKeyPropertyValue(EditorValidationInput input, AiProviderFamily family) {
return switch (family) {
case CLAUDE -> input.claudeApiKeyPropertyValue();
case OPENAI_COMPATIBLE -> input.openaiApiKeyPropertyValue();
};
}
/**
* Liest den bereits aufgelösten {@link EffectiveApiKeyDescriptor} für die aktive Provider-Familie
* direkt aus dem {@link EditorValidationInput}.
@@ -41,10 +41,12 @@ class EditorConfigurationValidatorTest {
"claude-3-5-sonnet", // claudeModel
"30", // claudeTimeoutSeconds
EffectiveApiKeyDescriptor.fromPropertyFile(), // claudeApiKeyDescriptor
"sk-claude", // claudeApiKeyPropertyValue
"https://api.openai.com", // openaiBaseUrl
"gpt-4", // openaiModel
"30", // openaiTimeoutSeconds
EffectiveApiKeyDescriptor.fromPropertyFile() // openaiApiKeyDescriptor
EffectiveApiKeyDescriptor.fromPropertyFile(), // openaiApiKeyDescriptor
"sk-openai" // openaiApiKeyPropertyValue
);
}
@@ -57,8 +59,8 @@ class EditorConfigurationValidatorTest {
EditorValidationInput input = new EditorValidationInput(
"", "C:/source", "C:/target", "C:/db.sqlite", "C:/prompt.txt",
"3", "10", "500",
"", "", "30", EffectiveApiKeyDescriptor.absent(),
"", "", "30", EffectiveApiKeyDescriptor.absent());
"", "", "30", EffectiveApiKeyDescriptor.absent(), "",
"", "", "30", EffectiveApiKeyDescriptor.absent(), "");
EditorValidationReport report = validator.validate(input);
@@ -74,8 +76,8 @@ class EditorConfigurationValidatorTest {
EditorValidationInput input = new EditorValidationInput(
"unknown-provider", "C:/source", "C:/target", "C:/db.sqlite", "C:/prompt.txt",
"3", "10", "500",
"", "", "30", EffectiveApiKeyDescriptor.absent(),
"", "", "30", EffectiveApiKeyDescriptor.absent());
"", "", "30", EffectiveApiKeyDescriptor.absent(), "",
"", "", "30", EffectiveApiKeyDescriptor.absent(), "");
EditorValidationReport report = validator.validate(input);
@@ -94,8 +96,8 @@ class EditorConfigurationValidatorTest {
"claude", "", "C:/target", "C:/db.sqlite", "C:/prompt.txt",
"3", "10", "500",
"https://api.anthropic.com", "claude-3-5-sonnet", "30",
EffectiveApiKeyDescriptor.fromPropertyFile(),
"", "", "30", EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-claude",
"", "", "30", EffectiveApiKeyDescriptor.absent(), "");
EditorValidationReport report = validator.validate(input);
@@ -109,8 +111,8 @@ class EditorConfigurationValidatorTest {
"claude", "C:/source", "", "C:/db.sqlite", "C:/prompt.txt",
"3", "10", "500",
"https://api.anthropic.com", "claude-3-5-sonnet", "30",
EffectiveApiKeyDescriptor.fromPropertyFile(),
"", "", "30", EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-claude",
"", "", "30", EffectiveApiKeyDescriptor.absent(), "");
EditorValidationReport report = validator.validate(input);
@@ -124,8 +126,8 @@ class EditorConfigurationValidatorTest {
"claude", "C:/source", "C:/target", "", "C:/prompt.txt",
"3", "10", "500",
"https://api.anthropic.com", "claude-3-5-sonnet", "30",
EffectiveApiKeyDescriptor.fromPropertyFile(),
"", "", "30", EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-claude",
"", "", "30", EffectiveApiKeyDescriptor.absent(), "");
EditorValidationReport report = validator.validate(input);
@@ -139,8 +141,8 @@ class EditorConfigurationValidatorTest {
"claude", "C:/source", "C:/target", "C:/db.sqlite", "",
"3", "10", "500",
"https://api.anthropic.com", "claude-3-5-sonnet", "30",
EffectiveApiKeyDescriptor.fromPropertyFile(),
"", "", "30", EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-claude",
"", "", "30", EffectiveApiKeyDescriptor.absent(), "");
EditorValidationReport report = validator.validate(input);
@@ -158,8 +160,8 @@ class EditorConfigurationValidatorTest {
"claude", "C:/source", "C:/target", "C:/db.sqlite", "C:/prompt.txt",
"0", "10", "500",
"https://api.anthropic.com", "claude-3-5-sonnet", "30",
EffectiveApiKeyDescriptor.fromPropertyFile(),
"", "", "30", EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-claude",
"", "", "30", EffectiveApiKeyDescriptor.absent(), "");
EditorValidationReport report = validator.validate(input);
@@ -173,8 +175,8 @@ class EditorConfigurationValidatorTest {
"claude", "C:/source", "C:/target", "C:/db.sqlite", "C:/prompt.txt",
"-1", "10", "500",
"https://api.anthropic.com", "claude-3-5-sonnet", "30",
EffectiveApiKeyDescriptor.fromPropertyFile(),
"", "", "30", EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-claude",
"", "", "30", EffectiveApiKeyDescriptor.absent(), "");
EditorValidationReport report = validator.validate(input);
@@ -188,8 +190,8 @@ class EditorConfigurationValidatorTest {
"claude", "C:/source", "C:/target", "C:/db.sqlite", "C:/prompt.txt",
"1", "10", "500",
"https://api.anthropic.com", "claude-3-5-sonnet", "30",
EffectiveApiKeyDescriptor.fromPropertyFile(),
"", "", "30", EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-claude",
"", "", "30", EffectiveApiKeyDescriptor.absent(), "");
EditorValidationReport report = validator.validate(input);
@@ -204,8 +206,8 @@ class EditorConfigurationValidatorTest {
"claude", "C:/source", "C:/target", "C:/db.sqlite", "C:/prompt.txt",
"abc", "10", "500",
"https://api.anthropic.com", "claude-3-5-sonnet", "30",
EffectiveApiKeyDescriptor.fromPropertyFile(),
"", "", "30", EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-claude",
"", "", "30", EffectiveApiKeyDescriptor.absent(), "");
EditorValidationReport report = validator.validate(input);
@@ -223,8 +225,8 @@ class EditorConfigurationValidatorTest {
"claude", "C:/source", "C:/target", "C:/db.sqlite", "C:/prompt.txt",
"3", "0", "500",
"https://api.anthropic.com", "claude-3-5-sonnet", "30",
EffectiveApiKeyDescriptor.fromPropertyFile(),
"", "", "30", EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-claude",
"", "", "30", EffectiveApiKeyDescriptor.absent(), "");
EditorValidationReport report = validator.validate(input);
@@ -238,8 +240,8 @@ class EditorConfigurationValidatorTest {
"claude", "C:/source", "C:/target", "C:/db.sqlite", "C:/prompt.txt",
"3", "101", "500",
"https://api.anthropic.com", "claude-3-5-sonnet", "30",
EffectiveApiKeyDescriptor.fromPropertyFile(),
"", "", "30", EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-claude",
"", "", "30", EffectiveApiKeyDescriptor.absent(), "");
EditorValidationReport report = validator.validate(input);
@@ -253,8 +255,8 @@ class EditorConfigurationValidatorTest {
"claude", "C:/source", "C:/target", "C:/db.sqlite", "C:/prompt.txt",
"3", "100", "500",
"https://api.anthropic.com", "claude-3-5-sonnet", "30",
EffectiveApiKeyDescriptor.fromPropertyFile(),
"", "", "30", EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-claude",
"", "", "30", EffectiveApiKeyDescriptor.absent(), "");
EditorValidationReport report = validator.validate(input);
@@ -275,8 +277,8 @@ class EditorConfigurationValidatorTest {
"claude", "C:/source", "C:/target", "C:/db.sqlite", "C:/prompt.txt",
"3", "10", "1000",
"https://api.anthropic.com", "claude-3-5-sonnet", "30",
EffectiveApiKeyDescriptor.fromPropertyFile(),
"", "", "30", EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-claude",
"", "", "30", EffectiveApiKeyDescriptor.absent(), "");
EditorValidationReport report = validator.validate(input);
@@ -290,8 +292,8 @@ class EditorConfigurationValidatorTest {
"claude", "C:/source", "C:/target", "C:/db.sqlite", "C:/prompt.txt",
"3", "10", "1001",
"https://api.anthropic.com", "claude-3-5-sonnet", "30",
EffectiveApiKeyDescriptor.fromPropertyFile(),
"", "", "30", EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-claude",
"", "", "30", EffectiveApiKeyDescriptor.absent(), "");
EditorValidationReport report = validator.validate(input);
@@ -305,8 +307,8 @@ class EditorConfigurationValidatorTest {
"claude", "C:/source", "C:/target", "C:/db.sqlite", "C:/prompt.txt",
"3", "10", "3000",
"https://api.anthropic.com", "claude-3-5-sonnet", "30",
EffectiveApiKeyDescriptor.fromPropertyFile(),
"", "", "30", EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-claude",
"", "", "30", EffectiveApiKeyDescriptor.absent(), "");
EditorValidationReport report = validator.validate(input);
@@ -320,8 +322,8 @@ class EditorConfigurationValidatorTest {
"claude", "C:/source", "C:/target", "C:/db.sqlite", "C:/prompt.txt",
"3", "10", "3001",
"https://api.anthropic.com", "claude-3-5-sonnet", "30",
EffectiveApiKeyDescriptor.fromPropertyFile(),
"", "", "30", EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-claude",
"", "", "30", EffectiveApiKeyDescriptor.absent(), "");
EditorValidationReport report = validator.validate(input);
@@ -340,8 +342,8 @@ class EditorConfigurationValidatorTest {
"claude", "C:/source", "C:/target", "C:/db.sqlite", "C:/prompt.txt",
"3", "10", "0",
"https://api.anthropic.com", "claude-3-5-sonnet", "30",
EffectiveApiKeyDescriptor.fromPropertyFile(),
"", "", "30", EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-claude",
"", "", "30", EffectiveApiKeyDescriptor.absent(), "");
EditorValidationReport report = validator.validate(input);
@@ -359,8 +361,8 @@ class EditorConfigurationValidatorTest {
"claude", "C:/source", "C:/target", "C:/db.sqlite", "C:/prompt.txt",
"3", "10", "500",
"https://api.anthropic.com", "", "30",
EffectiveApiKeyDescriptor.fromPropertyFile(),
"", "", "30", EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-claude",
"", "", "30", EffectiveApiKeyDescriptor.absent(), "");
EditorValidationReport report = validator.validate(input);
@@ -374,8 +376,8 @@ class EditorConfigurationValidatorTest {
"claude", "C:/source", "C:/target", "C:/db.sqlite", "C:/prompt.txt",
"3", "10", "500",
"", "claude-3-5-sonnet", "30",
EffectiveApiKeyDescriptor.fromPropertyFile(),
"", "", "30", EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-claude",
"", "", "30", EffectiveApiKeyDescriptor.absent(), "");
EditorValidationReport report = validator.validate(input);
@@ -389,8 +391,8 @@ class EditorConfigurationValidatorTest {
"claude", "C:/source", "C:/target", "C:/db.sqlite", "C:/prompt.txt",
"3", "10", "500",
"https://api.anthropic.com", "claude-3-5-sonnet", "-5",
EffectiveApiKeyDescriptor.fromPropertyFile(),
"", "", "30", EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-claude",
"", "", "30", EffectiveApiKeyDescriptor.absent(), "");
EditorValidationReport report = validator.validate(input);
@@ -408,8 +410,8 @@ class EditorConfigurationValidatorTest {
"claude", "C:/source", "C:/target", "C:/db.sqlite", "C:/prompt.txt",
"3", "10", "500",
"https://api.anthropic.com", "claude-3-5-sonnet", "30",
EffectiveApiKeyDescriptor.absent(),
"", "", "30", EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.absent(), "",
"", "", "30", EffectiveApiKeyDescriptor.absent(), "");
EditorValidationReport report = validator.validate(input);
@@ -434,8 +436,8 @@ class EditorConfigurationValidatorTest {
"claude", "C:/source", "C:/target", "C:/db.sqlite", "C:/prompt.txt",
"3", "10", "500",
"https://api.anthropic.com", "claude-3-5-sonnet", "30",
EffectiveApiKeyDescriptor.fromProviderEnvVar("ANTHROPIC_API_KEY"),
"", "", "30", EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.fromProviderEnvVar("ANTHROPIC_API_KEY"), "",
"", "", "30", EffectiveApiKeyDescriptor.absent(), "");
EditorValidationReport report = validator.validate(input);
@@ -449,9 +451,9 @@ class EditorConfigurationValidatorTest {
EditorValidationInput input = new EditorValidationInput(
"openai-compatible", "C:/source", "C:/target", "C:/db.sqlite", "C:/prompt.txt",
"3", "10", "500",
"", "", "30", EffectiveApiKeyDescriptor.absent(),
"", "", "30", EffectiveApiKeyDescriptor.absent(), "",
"https://api.openai.com", "gpt-4", "30",
EffectiveApiKeyDescriptor.fromLegacyEnvVar("PDF_UMBENENNER_API_KEY"));
EffectiveApiKeyDescriptor.fromLegacyEnvVar("PDF_UMBENENNER_API_KEY"), "");
EditorValidationReport report = validator.validate(input);
@@ -478,9 +480,9 @@ class EditorConfigurationValidatorTest {
EditorValidationInput input = new EditorValidationInput(
"openai-compatible", "C:/source", "C:/target", "C:/db.sqlite", "C:/prompt.txt",
"3", "10", "500",
"", "", "30", EffectiveApiKeyDescriptor.absent(),
"", "", "30", EffectiveApiKeyDescriptor.absent(), "",
"https://api.openai.com", "gpt-4", "30",
EffectiveApiKeyDescriptor.fromPropertyFile());
EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-openai");
EditorValidationReport report = validator.validate(input);
@@ -498,8 +500,8 @@ class EditorConfigurationValidatorTest {
"claude", "C:/source", "C:/target", "C:/db.sqlite", "C:/prompt.txt",
"3", "10", "500",
"https://api.anthropic.com", "claude-3-5-sonnet", "30",
EffectiveApiKeyDescriptor.fromPropertyFile(),
"", "", "", EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-claude",
"", "", "", EffectiveApiKeyDescriptor.absent(), "");
EditorValidationReport report = validator.validate(input);
@@ -29,9 +29,9 @@ class ProviderTechnicalTestServiceTest {
"/src", "/tgt", "/db.sqlite", "/prompt.txt",
"3", "10", "2000",
"https://api.anthropic.com", model, "30",
apiKeyDescriptor,
apiKeyDescriptor, "sk-test",
"https://api.openai.com", "gpt-4", "30",
EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.absent(), "");
}
private static EditorValidationInput openaiInput(EffectiveApiKeyDescriptor apiKeyDescriptor,
@@ -41,9 +41,9 @@ class ProviderTechnicalTestServiceTest {
"/src", "/tgt", "/db.sqlite", "/prompt.txt",
"3", "10", "2000",
"https://api.anthropic.com", "claude-3-sonnet", "30",
EffectiveApiKeyDescriptor.absent(),
EffectiveApiKeyDescriptor.absent(), "",
"https://api.openai.com", model, "30",
apiKeyDescriptor);
apiKeyDescriptor, "sk-test");
}
private static EffectiveApiKeyDescriptor keyFromEnv() {
@@ -60,11 +60,32 @@ class ProviderTechnicalTestServiceTest {
/**
* Stub-Port der immer den über den Konstruktor übergebenen Descriptor zurückgibt,
* unabhängig von family und propertyValue.
* unabhängig von family und propertyValue. Liefert den Roh-API-Schlüssel aus
* {@code propertyValue}, falls gesetzt; sonst einen festen Stub-Wert, sobald der
* Descriptor eine Quelle meldet.
*/
private static de.gecheckt.pdf.umbenenner.application.validation.editor.ApiKeyResolutionPort
apiKeyPort(EffectiveApiKeyDescriptor descriptor) {
return (family, propertyValue) -> descriptor;
return new de.gecheckt.pdf.umbenenner.application.validation.editor.ApiKeyResolutionPort() {
@Override
public EffectiveApiKeyDescriptor resolve(
de.gecheckt.pdf.umbenenner.application.config.provider.AiProviderFamily family,
String propertyValue) {
return descriptor;
}
@Override
public java.util.Optional<String> resolveEffectiveApiKeyValue(
de.gecheckt.pdf.umbenenner.application.config.provider.AiProviderFamily family,
String propertyValue) {
if (propertyValue != null && !propertyValue.isBlank()) {
return java.util.Optional.of(propertyValue);
}
return descriptor.isAbsent()
? java.util.Optional.empty()
: java.util.Optional.of("stub-key");
}
};
}
/**
@@ -388,9 +409,9 @@ class ProviderTechnicalTestServiceTest {
"/src", "/tgt", "/db.sqlite", "/prompt.txt",
"3", "10", "2000",
"", "model", "30",
EffectiveApiKeyDescriptor.absent(),
EffectiveApiKeyDescriptor.absent(), "",
"", "model", "30",
EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.absent(), "");
List<CheckpointResult> results = service.runProviderChecks(input);
@@ -31,9 +31,9 @@ class TechnicalTestOrchestratorTest {
"/src", "/tgt", "/db.sqlite", "/prompt.txt",
"3", "10", "500",
"https://api.anthropic.com", "claude-3-sonnet", "30",
EffectiveApiKeyDescriptor.fromPropertyFile(),
EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-test",
"https://api.openai.com", "gpt-4", "30",
EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.absent(), "");
}
/**
@@ -45,9 +45,9 @@ class TechnicalTestOrchestratorTest {
"", "", "", "",
"", "", "",
"", "", "",
EffectiveApiKeyDescriptor.absent(),
EffectiveApiKeyDescriptor.absent(), "",
"", "", "",
EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.absent(), "");
}
/** No-op {@link PathCheckPort}: alle Prüfungen liefern {@code false}. */
@@ -372,9 +372,9 @@ class TechnicalTestOrchestratorTest {
"", // kein Prompt-Pfad
"3", "10", "500",
"https://api.anthropic.com", "claude-3-sonnet", "30",
EffectiveApiKeyDescriptor.fromPropertyFile(),
EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-test",
"https://api.openai.com", "gpt-4", "30",
EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.absent(), "");
// PathCheckPort: Dateien fehlen, aber Elternordner sind schreibbar
PathCheckPort pathPort = new PathCheckPort() {
@@ -417,9 +417,9 @@ class TechnicalTestOrchestratorTest {
"", // kein Prompt-Pfad
"3", "10", "500",
"https://api.anthropic.com", "claude-3-sonnet", "30",
EffectiveApiKeyDescriptor.fromPropertyFile(),
EffectiveApiKeyDescriptor.fromPropertyFile(), "sk-test",
"https://api.openai.com", "gpt-4", "30",
EffectiveApiKeyDescriptor.absent());
EffectiveApiKeyDescriptor.absent(), "");
PathCheckPort pathPort = new PathCheckPort() {
@Override public boolean isDirectoryReadable(String p) { return true; }
@@ -16,8 +16,8 @@ class TechnicalTestRequestTest {
private static EditorValidationInput minimalInput() {
return new EditorValidationInput(
"claude", "", "", "", "", "3", "10", "2000",
"", "model-x", "60", EffectiveApiKeyDescriptor.absent(),
"", "", "60", EffectiveApiKeyDescriptor.absent());
"", "model-x", "60", EffectiveApiKeyDescriptor.absent(), "",
"", "", "60", EffectiveApiKeyDescriptor.absent(), "");
}
@Test