diff --git a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiConfigurationEditorWorkspace.java b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiConfigurationEditorWorkspace.java index 470df4e..3f93f0c 100644 --- a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiConfigurationEditorWorkspace.java +++ b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiConfigurationEditorWorkspace.java @@ -1967,14 +1967,14 @@ public final class GuiConfigurationEditorWorkspace { /** * Updates the API-key origin labels below each provider's API-key field. *

- * The display logic follows the effective-key precedence order: - *

    - *
  1. If the effective key comes from an environment variable, the label shows the - * variable name in an informational style.
  2. - *
  3. If the property value is filled and no environment variable overrides it, the label - * is hidden.
  4. - *
  5. If no key source is effective, the label shows a warning in the field-error style.
  6. - *
+ * The label is shown exclusively when the effective key comes from an environment variable + * (INFO finding). In all other cases the label is hidden: + * *

* The origin information is derived from the pending field findings produced by the validator, * which already resolved the API-key precedence for both providers. @@ -1988,28 +1988,20 @@ public final class GuiConfigurationEditorWorkspace { String apiKeyField = "ai.provider." + family.getIdentifier() + ".apiKey"; // Look for an INFO finding about ENV-variable origin for this provider. + // WARNING/ERROR findings (missing key) are intentionally excluded here: + // those are already shown by the field-error label registered in fieldErrorLabels, + // and repeating them in the origin label would cause the same text to appear twice + // in close proximity below the API-key field. Optional envFinding = pendingFieldFindings.stream() .filter(f -> apiKeyField.equals(f.fieldKey())) .filter(f -> f.severity() == GuiMessageSeverity.INFO) .findFirst(); - // Look for an ERROR/WARNING finding about missing key for this provider. - Optional missingFinding = pendingFieldFindings.stream() - .filter(f -> apiKeyField.equals(f.fieldKey())) - .filter(f -> f.severity() == GuiMessageSeverity.ERROR - || f.severity() == GuiMessageSeverity.WARNING) - .findFirst(); - if (envFinding.isPresent()) { label.setText(envFinding.get().text()); label.setStyle(STYLE_ORIGIN_INFO); label.setVisible(true); label.setManaged(true); - } else if (missingFinding.isPresent()) { - label.setText(missingFinding.get().text()); - label.setStyle(STYLE_ORIGIN_MISSING); - label.setVisible(true); - label.setManaged(true); } else { label.setText(""); label.setVisible(false); @@ -2020,8 +2012,6 @@ public final class GuiConfigurationEditorWorkspace { private static final String STYLE_ORIGIN_INFO = "-fx-font-size: 11px; -fx-text-fill: #1565c0; -fx-padding: 0 0 4px 0;"; - private static final String STYLE_ORIGIN_MISSING = - "-fx-font-size: 11px; -fx-text-fill: #b71c1c; -fx-padding: 0 0 4px 0;"; // ========================================================================= // Path picker helpers diff --git a/pdf-umbenenner-adapter-in-gui/src/test/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiMessageAreaSmokeTest.java b/pdf-umbenenner-adapter-in-gui/src/test/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiMessageAreaSmokeTest.java index 6a06eb2..925f1fd 100644 --- a/pdf-umbenenner-adapter-in-gui/src/test/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiMessageAreaSmokeTest.java +++ b/pdf-umbenenner-adapter-in-gui/src/test/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiMessageAreaSmokeTest.java @@ -361,6 +361,79 @@ class GuiMessageAreaSmokeTest { }); } + // ========================================================================= + // Scenario: absent API key — origin label hidden, no duplicate in message area + // ========================================================================= + + /** + * Smoke test: when no API key is configured and no environment variable provides one, the + * api-key origin label below the Claude API-key field must be hidden (not visible). + *

+ * The field-error label (registered in {@code fieldErrorLabels}) already shows the warning + * below the input field; duplicating the same text in the origin label would cause the same + * message to appear twice in close visual proximity. The origin label is therefore hidden + * for the absent-key case. + */ + @Test + void apiKeyAbsent_originLabelHidden() throws Exception { + runOnFx(() -> { + GuiConfigurationEditorWorkspace ws = new GuiConfigurationEditorWorkspace(Optional.empty()); + ws.requestNewConfiguration(); + + // Standard template has active provider = Claude but no API key value. + Label originLabel = ws.apiKeyOriginLabels.get(AiProviderFamily.CLAUDE); + assertNotNull(originLabel, + "An api-key origin label must be registered for AiProviderFamily.CLAUDE"); + assertFalse(originLabel.isVisible(), + "Claude api-key origin label must be HIDDEN when no API key is configured " + + "— the field-error label already shows the warning, so the origin label " + + "must not repeat it"); + }); + } + + /** + * Smoke test: when no API key is configured, the field-error label registered for + * {@code ai.provider.claude.apiKey} must be visible and carry a non-blank warning text. + *

+ * This is the single intended location for the missing-key warning below the input field. + */ + @Test + void apiKeyAbsent_fieldErrorLabelVisible() throws Exception { + runOnFx(() -> { + GuiConfigurationEditorWorkspace ws = new GuiConfigurationEditorWorkspace(Optional.empty()); + ws.requestNewConfiguration(); + + Label errorLabel = ws.fieldErrorLabels.get("ai.provider.claude.apiKey"); + assertNotNull(errorLabel, + "A field-error label must be registered for 'ai.provider.claude.apiKey'"); + assertTrue(errorLabel.isVisible(), + "api-key field-error label must be visible when no API key is configured"); + assertFalse(errorLabel.getText().isBlank(), + "api-key field-error label must carry a non-blank warning text"); + }); + } + + /** + * Smoke test: when no API key is configured, the text of the missing-key warning must appear + * exactly once in {@code pendingMessages}. Having the same text twice would mean two + * validation sources independently write the same finding to the central message area. + */ + @Test + void apiKeyAbsent_noDuplicateMessageInPendingMessages() throws Exception { + runOnFx(() -> { + GuiConfigurationEditorWorkspace ws = new GuiConfigurationEditorWorkspace(Optional.empty()); + ws.requestNewConfiguration(); + + // Collect all message texts that contain the canonical "Kein API-Schlüssel" fragment. + long count = ws.pendingMessages.stream() + .filter(m -> m.text().contains("API-Schlüssel") || m.text().contains("API-Key")) + .count(); + assertTrue(count <= 1, + "The missing-API-key finding must appear at most once in pendingMessages, " + + "but found " + count + " occurrences"); + }); + } + // ========================================================================= // Scenario: INFO-coloured prefix in the message area (model-catalogue success) // =========================================================================