diff --git a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/AiFailureMessageTranslator.java b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/AiFailureMessageTranslator.java new file mode 100644 index 0000000..fe48077 --- /dev/null +++ b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/AiFailureMessageTranslator.java @@ -0,0 +1,54 @@ +package de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun; + +/** + * Übersetzt technische KI-Fehlermeldungen in benutzerfreundliche deutsche Texte. + *
+ * Die Klasse wertet die rohe, englischsprachige Fehlermeldung aus dem + * Verarbeitungsversuch aus und liefert eine für den Endbenutzer lesbare + * Beschreibung des Fehlergrunds. Das ursprüngliche Datenmodell bleibt + * unverändert; die Übersetzung findet ausschließlich in der Darstellungsschicht + * statt. + *
+ * Die Mustererkennung erfolgt ohne Berücksichtigung der Groß-/Kleinschreibung + * und prüft die definierten Schlüsselbegriffe in festgelegter Reihenfolge, + * damit spezifischere Muster vor allgemeineren greifen. + */ +final class AiFailureMessageTranslator { + + private AiFailureMessageTranslator() { + } + + /** + * Liefert eine benutzerfreundliche deutsche Fehlermeldung für die angegebene + * technische Fehlerbeschreibung. + *
+ * Ist {@code technicalMessage} {@code null} oder leer, wird der allgemeine + * Fallback-Text zurückgegeben. + * + * @param technicalMessage die rohe technische Fehlermeldung; darf {@code null} sein + * @return eine nicht-leere deutsche Benutzerfehlermeldung + */ + static String translate(String technicalMessage) { + if (technicalMessage == null || technicalMessage.isBlank()) { + return "KI-Aufruf fehlgeschlagen. Details im Anwendungslog."; + } + String lower = technicalMessage.toLowerCase(java.util.Locale.ROOT); + + if (lower.contains("http_401")) { + return "KI-Dienst: Ungültiger API-Schlüssel. Bitte in den Einstellungen prüfen."; + } + if (lower.contains("http_403")) { + return "KI-Dienst: Zugriff verweigert. Bitte API-Schlüssel und Berechtigungen prüfen."; + } + if (lower.contains("http_429")) { + return "KI-Dienst: Anfragelimit erreicht. Bitte später erneut versuchen."; + } + if (lower.contains("http_5")) { + return "KI-Dienst vorübergehend nicht erreichbar. Bitte später erneut versuchen."; + } + if (lower.contains("connection") || lower.contains("timeout") || lower.contains("refused")) { + return "KI-Dienst nicht erreichbar. Bitte Netzwerkverbindung prüfen."; + } + return "KI-Aufruf fehlgeschlagen. Details im Anwendungslog."; + } +} diff --git a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/GuiBatchRunTab.java b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/GuiBatchRunTab.java index 7e6df15..d993c9a 100644 --- a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/GuiBatchRunTab.java +++ b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/GuiBatchRunTab.java @@ -901,7 +901,9 @@ public final class GuiBatchRunTab { reasoning -> builder.append(reasoning), () -> { row.aiFailureMessage().ifPresent(msg -> - builder.append("\u26A0 Fehler: ").append(msg).append("\n\n")); + builder.append("\u26A0 Fehler: ") + .append(AiFailureMessageTranslator.translate(msg)) + .append("\n\n")); builder.append(NO_REASONING_TEXT); }); return builder.toString(); diff --git a/pdf-umbenenner-adapter-in-gui/src/test/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/AiFailureMessageTranslatorTest.java b/pdf-umbenenner-adapter-in-gui/src/test/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/AiFailureMessageTranslatorTest.java new file mode 100644 index 0000000..98ce3b3 --- /dev/null +++ b/pdf-umbenenner-adapter-in-gui/src/test/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/AiFailureMessageTranslatorTest.java @@ -0,0 +1,135 @@ +package de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun; + +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 org.junit.jupiter.api.Test; + +/** + * Unit-Tests für {@link AiFailureMessageTranslator}. + *
+ * Prüft alle definierten Mapping-Fälle sowie Randbedingungen wie + * {@code null}-Eingaben und unbekannte Fehlertexte. + */ +class AiFailureMessageTranslatorTest { + + // ------------------------------------------------------------------------- + // HTTP-Statuscodes + // ------------------------------------------------------------------------- + + @Test + void http401_liefertApiSchluesselMeldung() { + String result = AiFailureMessageTranslator.translate( + "Processing failed (retryable). AI technical error: AI invocation failed [HTTP_401]: AI service returned status 401"); + assertEquals("KI-Dienst: Ungültiger API-Schlüssel. Bitte in den Einstellungen prüfen.", result); + } + + @Test + void http401_kleinschreibung_wirdErkannt() { + String result = AiFailureMessageTranslator.translate("http_401 ungültig"); + assertEquals("KI-Dienst: Ungültiger API-Schlüssel. Bitte in den Einstellungen prüfen.", result); + } + + @Test + void http403_liefertZugriffVerweigertMeldung() { + String result = AiFailureMessageTranslator.translate("HTTP_403 Forbidden"); + assertEquals("KI-Dienst: Zugriff verweigert. Bitte API-Schlüssel und Berechtigungen prüfen.", result); + } + + @Test + void http429_liefertRateLimitMeldung() { + String result = AiFailureMessageTranslator.translate("AI invocation failed [HTTP_429]: rate limit"); + assertEquals("KI-Dienst: Anfragelimit erreicht. Bitte später erneut versuchen.", result); + } + + @Test + void http500_liefertServerFehlerMeldung() { + String result = AiFailureMessageTranslator.translate("AI invocation failed [HTTP_500]: internal error"); + assertEquals("KI-Dienst vorübergehend nicht erreichbar. Bitte später erneut versuchen.", result); + } + + @Test + void http503_liefertServerFehlerMeldung() { + String result = AiFailureMessageTranslator.translate("HTTP_503 Service Unavailable"); + assertEquals("KI-Dienst vorübergehend nicht erreichbar. Bitte später erneut versuchen.", result); + } + + // ------------------------------------------------------------------------- + // Verbindungsfehler + // ------------------------------------------------------------------------- + + @Test + void connectionFehler_liefertNetzwerkMeldung() { + String result = AiFailureMessageTranslator.translate("Connection refused to https://api.example.com"); + assertEquals("KI-Dienst nicht erreichbar. Bitte Netzwerkverbindung prüfen.", result); + } + + @Test + void timeoutFehler_liefertNetzwerkMeldung() { + String result = AiFailureMessageTranslator.translate("AI technical error: timeout after 30s"); + assertEquals("KI-Dienst nicht erreichbar. Bitte Netzwerkverbindung prüfen.", result); + } + + @Test + void refusedFehler_liefertNetzwerkMeldung() { + String result = AiFailureMessageTranslator.translate("connect refused"); + assertEquals("KI-Dienst nicht erreichbar. Bitte Netzwerkverbindung prüfen.", result); + } + + // ------------------------------------------------------------------------- + // Fallback + // ------------------------------------------------------------------------- + + @Test + void unbekannterFehler_liefertFallback() { + String result = AiFailureMessageTranslator.translate("some completely unknown error text"); + assertEquals("KI-Aufruf fehlgeschlagen. Details im Anwendungslog.", result); + } + + @Test + void null_liefertFallback() { + String result = AiFailureMessageTranslator.translate(null); + assertEquals("KI-Aufruf fehlgeschlagen. Details im Anwendungslog.", result); + } + + @Test + void leerstring_liefertFallback() { + String result = AiFailureMessageTranslator.translate(""); + assertEquals("KI-Aufruf fehlgeschlagen. Details im Anwendungslog.", result); + } + + @Test + void nurLeerzeichen_liefertFallback() { + String result = AiFailureMessageTranslator.translate(" "); + assertEquals("KI-Aufruf fehlgeschlagen. Details im Anwendungslog.", result); + } + + // ------------------------------------------------------------------------- + // Allgemeine Qualität der Rückgabe + // ------------------------------------------------------------------------- + + @Test + void alleEingaben_liefernNichtLeereTexte() { + String[] eingaben = { + null, "", " ", "HTTP_401", "HTTP_403", "HTTP_429", "HTTP_500", + "connection error", "timeout", "refused", "unbekannt" + }; + for (String eingabe : eingaben) { + String result = AiFailureMessageTranslator.translate(eingabe); + assertNotNull(result, "Ergebnis darf nicht null sein für: " + eingabe); + assertTrue(!result.isBlank(), "Ergebnis darf nicht leer sein für: " + eingabe); + } + } + + // ------------------------------------------------------------------------- + // Reihenfolge: HTTP_401 hat Vorrang vor HTTP_5xx + // ------------------------------------------------------------------------- + + @Test + void http401VorHttp5xx_http401GewinntNicht_daKeinHttp5Enthalten() { + // HTTP_401 enthält kein "http_5", daher gilt 401-Regel + String result = AiFailureMessageTranslator.translate("HTTP_401"); + assertEquals("KI-Dienst: Ungültiger API-Schlüssel. Bitte in den Einstellungen prüfen.", result); + } +}