Titellänge nun parametrisierbar
This commit is contained in:
+7
-1
@@ -1370,7 +1370,7 @@ public final class GuiConfigurationEditorWorkspace {
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Builds the "Verarbeitungslimits" section with text fields for the three numeric limit
|
||||
* Builds the "Verarbeitungslimits" section with text fields for the numeric limit
|
||||
* parameters and a checkbox for the sensitive-logging flag.
|
||||
*
|
||||
* @return the card node for the "Verarbeitungslimits" section
|
||||
@@ -1392,6 +1392,11 @@ public final class GuiConfigurationEditorWorkspace {
|
||||
val -> updateValues(editorState.values().withMaxTextCharacters(val)));
|
||||
addSimpleRow(grid, row++, "Maximale Zeichenzahl:", maxCharsField);
|
||||
|
||||
TextField maxTitleLengthField = boundTextField(
|
||||
editorState.values().maxTitleLength(),
|
||||
val -> updateValues(editorState.values().withMaxTitleLength(val)));
|
||||
addSimpleRow(grid, row++, "Max. Titellänge (Zeichen):", maxTitleLengthField);
|
||||
|
||||
TextField maxRetriesField = boundTextField(
|
||||
editorState.values().maxRetriesTransient(),
|
||||
val -> updateValues(editorState.values().withMaxRetriesTransient(val)));
|
||||
@@ -1587,6 +1592,7 @@ public final class GuiConfigurationEditorWorkspace {
|
||||
values.maxRetriesTransient(),
|
||||
values.maxPages(),
|
||||
values.maxTextCharacters(),
|
||||
values.maxTitleLength(),
|
||||
claudeState.baseUrl(),
|
||||
claudeState.model(),
|
||||
claudeState.timeoutSeconds(),
|
||||
|
||||
+1
@@ -92,6 +92,7 @@ public final class GuiApiKeyMerger {
|
||||
current.maxRetriesTransient(),
|
||||
current.maxPages(),
|
||||
current.maxTextCharacters(),
|
||||
current.maxTitleLength(),
|
||||
current.logAiSensitive(),
|
||||
current.activeProviderFamily(),
|
||||
merged);
|
||||
|
||||
+2
@@ -25,6 +25,7 @@ public final class GuiConfigurationEditorStateFactory {
|
||||
private static final String PROP_MAX_RETRIES_TRANSIENT = "max.retries.transient";
|
||||
private static final String PROP_MAX_PAGES = "max.pages";
|
||||
private static final String PROP_MAX_TEXT_CHARACTERS = "max.text.characters";
|
||||
private static final String PROP_MAX_TITLE_LENGTH = "max.title.length";
|
||||
private static final String PROP_LOG_AI_SENSITIVE = "log.ai.sensitive";
|
||||
private static final String PROP_ACTIVE_PROVIDER = "ai.provider.active";
|
||||
private static final String PROP_CLAUDE_BASE_URL = "ai.provider.claude.baseUrl";
|
||||
@@ -74,6 +75,7 @@ public final class GuiConfigurationEditorStateFactory {
|
||||
propertyOrBlank(properties, PROP_MAX_RETRIES_TRANSIENT),
|
||||
propertyOrBlank(properties, PROP_MAX_PAGES),
|
||||
propertyOrBlank(properties, PROP_MAX_TEXT_CHARACTERS),
|
||||
propertyOrBlank(properties, PROP_MAX_TITLE_LENGTH),
|
||||
propertyOrBlank(properties, PROP_LOG_AI_SENSITIVE),
|
||||
propertyOrBlank(properties, PROP_ACTIVE_PROVIDER),
|
||||
providerConfigurations);
|
||||
|
||||
+3
@@ -24,6 +24,7 @@ public final class GuiConfigurationTemplateFactory {
|
||||
private static final String MAX_RETRIES_TRANSIENT = "3";
|
||||
private static final String MAX_PAGES = "10";
|
||||
private static final String MAX_TEXT_CHARACTERS = "5000";
|
||||
private static final String DEFAULT_MAX_TITLE_LENGTH = "60";
|
||||
|
||||
private static final String OPENAI_BASE_URL = "https://api.openai.com/v1";
|
||||
private static final String OPENAI_MODEL = "gpt-4o-mini";
|
||||
@@ -83,6 +84,7 @@ public final class GuiConfigurationTemplateFactory {
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
Map.of());
|
||||
return new GuiConfigurationEditorState(Optional.empty(), blankValues, blankValues, Optional.empty());
|
||||
}
|
||||
@@ -116,6 +118,7 @@ public final class GuiConfigurationTemplateFactory {
|
||||
MAX_RETRIES_TRANSIENT,
|
||||
MAX_PAGES,
|
||||
MAX_TEXT_CHARACTERS,
|
||||
DEFAULT_MAX_TITLE_LENGTH,
|
||||
Boolean.toString(false),
|
||||
AiProviderFamily.CLAUDE.getIdentifier(),
|
||||
providerConfigurations);
|
||||
|
||||
+29
-13
@@ -23,6 +23,7 @@ import de.gecheckt.pdf.umbenenner.application.config.provider.AiProviderFamily;
|
||||
* @param maxRetriesTransient transient retry limit as editable text
|
||||
* @param maxPages page limit as editable text
|
||||
* @param maxTextCharacters text limit as editable text
|
||||
* @param maxTitleLength maximum base-title length as editable text
|
||||
* @param logAiSensitive raw value of {@code log.ai.sensitive} as editable text
|
||||
* @param activeProviderFamily raw value of {@code ai.provider.active} as editable text
|
||||
* @param providerConfigurations provider-specific editor state keyed by provider family
|
||||
@@ -38,6 +39,7 @@ public record GuiConfigurationValues(
|
||||
String maxRetriesTransient,
|
||||
String maxPages,
|
||||
String maxTextCharacters,
|
||||
String maxTitleLength,
|
||||
String logAiSensitive,
|
||||
String activeProviderFamily,
|
||||
Map<AiProviderFamily, GuiProviderConfigurationState> providerConfigurations) {
|
||||
@@ -55,6 +57,7 @@ public record GuiConfigurationValues(
|
||||
* @param maxRetriesTransient transient retry limit; {@code null} becomes an empty string
|
||||
* @param maxPages page limit; {@code null} becomes an empty string
|
||||
* @param maxTextCharacters text limit; {@code null} becomes an empty string
|
||||
* @param maxTitleLength maximum base-title length; {@code null} becomes an empty string
|
||||
* @param logAiSensitive raw {@code log.ai.sensitive} value; {@code null} becomes an empty string
|
||||
* @param activeProviderFamily raw {@code ai.provider.active} value; {@code null} becomes an empty string
|
||||
* @param providerConfigurations provider-specific state map; must not be {@code null}
|
||||
@@ -70,6 +73,7 @@ public record GuiConfigurationValues(
|
||||
maxRetriesTransient = normalizeText(maxRetriesTransient);
|
||||
maxPages = normalizeText(maxPages);
|
||||
maxTextCharacters = normalizeText(maxTextCharacters);
|
||||
maxTitleLength = normalizeText(maxTitleLength);
|
||||
logAiSensitive = normalizeText(logAiSensitive);
|
||||
activeProviderFamily = normalizeText(activeProviderFamily);
|
||||
|
||||
@@ -98,7 +102,7 @@ public record GuiConfigurationValues(
|
||||
public GuiConfigurationValues withActiveProviderFamily(String providerFamily) {
|
||||
return new GuiConfigurationValues(sourceFolder, targetFolder, sqliteFile, promptTemplateFile,
|
||||
runtimeLockFile, logDirectory, logLevel, maxRetriesTransient, maxPages, maxTextCharacters,
|
||||
logAiSensitive, providerFamily, providerConfigurations);
|
||||
maxTitleLength, logAiSensitive, providerFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,7 +114,7 @@ public record GuiConfigurationValues(
|
||||
public GuiConfigurationValues withSourceFolder(String value) {
|
||||
return new GuiConfigurationValues(value, targetFolder, sqliteFile, promptTemplateFile,
|
||||
runtimeLockFile, logDirectory, logLevel, maxRetriesTransient, maxPages, maxTextCharacters,
|
||||
logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
maxTitleLength, logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,7 +126,7 @@ public record GuiConfigurationValues(
|
||||
public GuiConfigurationValues withTargetFolder(String value) {
|
||||
return new GuiConfigurationValues(sourceFolder, value, sqliteFile, promptTemplateFile,
|
||||
runtimeLockFile, logDirectory, logLevel, maxRetriesTransient, maxPages, maxTextCharacters,
|
||||
logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
maxTitleLength, logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,7 +138,7 @@ public record GuiConfigurationValues(
|
||||
public GuiConfigurationValues withSqliteFile(String value) {
|
||||
return new GuiConfigurationValues(sourceFolder, targetFolder, value, promptTemplateFile,
|
||||
runtimeLockFile, logDirectory, logLevel, maxRetriesTransient, maxPages, maxTextCharacters,
|
||||
logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
maxTitleLength, logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,7 +150,7 @@ public record GuiConfigurationValues(
|
||||
public GuiConfigurationValues withPromptTemplateFile(String value) {
|
||||
return new GuiConfigurationValues(sourceFolder, targetFolder, sqliteFile, value,
|
||||
runtimeLockFile, logDirectory, logLevel, maxRetriesTransient, maxPages, maxTextCharacters,
|
||||
logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
maxTitleLength, logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,7 +162,7 @@ public record GuiConfigurationValues(
|
||||
public GuiConfigurationValues withRuntimeLockFile(String value) {
|
||||
return new GuiConfigurationValues(sourceFolder, targetFolder, sqliteFile, promptTemplateFile,
|
||||
value, logDirectory, logLevel, maxRetriesTransient, maxPages, maxTextCharacters,
|
||||
logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
maxTitleLength, logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,7 +174,7 @@ public record GuiConfigurationValues(
|
||||
public GuiConfigurationValues withLogDirectory(String value) {
|
||||
return new GuiConfigurationValues(sourceFolder, targetFolder, sqliteFile, promptTemplateFile,
|
||||
runtimeLockFile, value, logLevel, maxRetriesTransient, maxPages, maxTextCharacters,
|
||||
logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
maxTitleLength, logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,7 +186,7 @@ public record GuiConfigurationValues(
|
||||
public GuiConfigurationValues withLogLevel(String value) {
|
||||
return new GuiConfigurationValues(sourceFolder, targetFolder, sqliteFile, promptTemplateFile,
|
||||
runtimeLockFile, logDirectory, value, maxRetriesTransient, maxPages, maxTextCharacters,
|
||||
logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
maxTitleLength, logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -194,7 +198,7 @@ public record GuiConfigurationValues(
|
||||
public GuiConfigurationValues withMaxRetriesTransient(String value) {
|
||||
return new GuiConfigurationValues(sourceFolder, targetFolder, sqliteFile, promptTemplateFile,
|
||||
runtimeLockFile, logDirectory, logLevel, value, maxPages, maxTextCharacters,
|
||||
logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
maxTitleLength, logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,7 +210,7 @@ public record GuiConfigurationValues(
|
||||
public GuiConfigurationValues withMaxPages(String value) {
|
||||
return new GuiConfigurationValues(sourceFolder, targetFolder, sqliteFile, promptTemplateFile,
|
||||
runtimeLockFile, logDirectory, logLevel, maxRetriesTransient, value, maxTextCharacters,
|
||||
logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
maxTitleLength, logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -218,7 +222,19 @@ public record GuiConfigurationValues(
|
||||
public GuiConfigurationValues withMaxTextCharacters(String value) {
|
||||
return new GuiConfigurationValues(sourceFolder, targetFolder, sqliteFile, promptTemplateFile,
|
||||
runtimeLockFile, logDirectory, logLevel, maxRetriesTransient, maxPages, value,
|
||||
logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
maxTitleLength, logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy with a different maximum base-title length value.
|
||||
*
|
||||
* @param value new value; {@code null} becomes an empty string
|
||||
* @return a new configuration values object with the requested title-length value
|
||||
*/
|
||||
public GuiConfigurationValues withMaxTitleLength(String value) {
|
||||
return new GuiConfigurationValues(sourceFolder, targetFolder, sqliteFile, promptTemplateFile,
|
||||
runtimeLockFile, logDirectory, logLevel, maxRetriesTransient, maxPages, maxTextCharacters,
|
||||
value, logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -230,7 +246,7 @@ public record GuiConfigurationValues(
|
||||
public GuiConfigurationValues withLogAiSensitive(String value) {
|
||||
return new GuiConfigurationValues(sourceFolder, targetFolder, sqliteFile, promptTemplateFile,
|
||||
runtimeLockFile, logDirectory, logLevel, maxRetriesTransient, maxPages, maxTextCharacters,
|
||||
value, activeProviderFamily, providerConfigurations);
|
||||
maxTitleLength, value, activeProviderFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -243,7 +259,7 @@ public record GuiConfigurationValues(
|
||||
Map<AiProviderFamily, GuiProviderConfigurationState> providerConfigurations) {
|
||||
return new GuiConfigurationValues(sourceFolder, targetFolder, sqliteFile, promptTemplateFile,
|
||||
runtimeLockFile, logDirectory, logLevel, maxRetriesTransient, maxPages, maxTextCharacters,
|
||||
logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
maxTitleLength, logAiSensitive, activeProviderFamily, providerConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -157,7 +157,7 @@ class GuiConfigurationEditorWorkspaceSaveTest {
|
||||
return new GuiConfigurationValues(
|
||||
"./source", "./target", "./db.sqlite", "./prompt.txt",
|
||||
"./app.lock", "./logs", "INFO", "3", "10", "5000",
|
||||
"false", "claude", providers);
|
||||
"60", "false", "claude", providers);
|
||||
}
|
||||
|
||||
private GuiConfigurationEditorState buildState(GuiConfigurationValues baseline,
|
||||
|
||||
+1
@@ -174,6 +174,7 @@ class GuiDirtyStateTest {
|
||||
v.maxRetriesTransient(),
|
||||
v.maxPages(),
|
||||
v.maxTextCharacters(),
|
||||
v.maxTitleLength(),
|
||||
v.logAiSensitive(),
|
||||
v.activeProviderFamily(),
|
||||
v.providerConfigurations());
|
||||
|
||||
+4
@@ -93,6 +93,8 @@ class GuiEditorFieldBindingTest {
|
||||
"Max pages must match the standard template default");
|
||||
assertEquals("5000", v.maxTextCharacters(),
|
||||
"Max text characters must match the standard template default");
|
||||
assertEquals("60", v.maxTitleLength(),
|
||||
"Max title length must match the standard template default");
|
||||
assertEquals("false", v.logAiSensitive(),
|
||||
"log.ai.sensitive must match the standard template default (false)");
|
||||
});
|
||||
@@ -422,6 +424,7 @@ class GuiEditorFieldBindingTest {
|
||||
.withMaxRetriesTransient("5")
|
||||
.withMaxPages("20")
|
||||
.withMaxTextCharacters("1000")
|
||||
.withMaxTitleLength("80")
|
||||
.withLogAiSensitive("true")
|
||||
.withActiveProviderFamily("openai-compatible");
|
||||
|
||||
@@ -435,6 +438,7 @@ class GuiEditorFieldBindingTest {
|
||||
assertEquals("5", modified.maxRetriesTransient());
|
||||
assertEquals("20", modified.maxPages());
|
||||
assertEquals("1000", modified.maxTextCharacters());
|
||||
assertEquals("80", modified.maxTitleLength());
|
||||
assertEquals("true", modified.logAiSensitive());
|
||||
assertEquals("openai-compatible", modified.activeProviderFamily());
|
||||
|
||||
|
||||
+157
@@ -431,6 +431,162 @@ class GuiEditorValidationSmokeTest {
|
||||
});
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Scenario: max.title.length – validation per value band
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Smoke test: when the standard template is applied and the title-length field is cleared
|
||||
* via the {@code withMaxTitleLength("")} copy, the local validation produces an ERROR
|
||||
* finding for {@code max.title.length}.
|
||||
*
|
||||
* @throws Exception if the FX thread task fails or times out
|
||||
*/
|
||||
@Test
|
||||
void emptyMaxTitleLength_producesFieldFindingError() throws Exception {
|
||||
runOnFx(() -> {
|
||||
GuiConfigurationEditorWorkspace ws =
|
||||
new GuiConfigurationEditorWorkspace(Optional.empty());
|
||||
ws.requestNewConfiguration();
|
||||
ws.editorState = ws.editorState().withValues(
|
||||
ws.editorState().values().withMaxTitleLength(""));
|
||||
ws.validateButton.fire();
|
||||
|
||||
assertNotNull(ws.lastValidationResult(),
|
||||
"lastValidationResult must not be null after editing");
|
||||
assertTrue(ws.lastValidationResult().hasFieldFindingFor("max.title.length"),
|
||||
"Clearing max.title.length must produce a field finding");
|
||||
boolean hasErrorForField = ws.lastValidationResult().fieldFindings().stream()
|
||||
.anyMatch(f -> "max.title.length".equals(f.fieldKey())
|
||||
&& f.severity() == GuiMessageSeverity.ERROR);
|
||||
assertTrue(hasErrorForField,
|
||||
"Empty max.title.length must be an ERROR for this field");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Smoke test: a too-small title-length value (below the minimum of 10) produces an ERROR
|
||||
* finding for the field.
|
||||
*
|
||||
* @throws Exception if the FX thread task fails or times out
|
||||
*/
|
||||
@Test
|
||||
void tooSmallMaxTitleLength_producesFieldFindingError() throws Exception {
|
||||
runOnFx(() -> {
|
||||
GuiConfigurationEditorWorkspace ws =
|
||||
new GuiConfigurationEditorWorkspace(Optional.empty());
|
||||
ws.requestNewConfiguration();
|
||||
ws.editorState = ws.editorState().withValues(
|
||||
ws.editorState().values().withMaxTitleLength("5"));
|
||||
ws.validateButton.fire();
|
||||
|
||||
assertTrue(ws.lastValidationResult().hasFieldFindingFor("max.title.length"),
|
||||
"Value below minimum must produce a field finding");
|
||||
boolean hasErrorForField = ws.lastValidationResult().fieldFindings().stream()
|
||||
.anyMatch(f -> "max.title.length".equals(f.fieldKey())
|
||||
&& f.severity() == GuiMessageSeverity.ERROR);
|
||||
assertTrue(hasErrorForField,
|
||||
"Value below minimum must be an ERROR for this field");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Smoke test: a too-large title-length value (above the upper limit of 120) produces an ERROR
|
||||
* finding for the field.
|
||||
*
|
||||
* @throws Exception if the FX thread task fails or times out
|
||||
*/
|
||||
@Test
|
||||
void tooLargeMaxTitleLength_producesFieldFindingError() throws Exception {
|
||||
runOnFx(() -> {
|
||||
GuiConfigurationEditorWorkspace ws =
|
||||
new GuiConfigurationEditorWorkspace(Optional.empty());
|
||||
ws.requestNewConfiguration();
|
||||
ws.editorState = ws.editorState().withValues(
|
||||
ws.editorState().values().withMaxTitleLength("200"));
|
||||
ws.validateButton.fire();
|
||||
|
||||
assertTrue(ws.lastValidationResult().hasFieldFindingFor("max.title.length"),
|
||||
"Value above safe maximum must produce a field finding");
|
||||
boolean hasErrorForField = ws.lastValidationResult().fieldFindings().stream()
|
||||
.anyMatch(f -> "max.title.length".equals(f.fieldKey())
|
||||
&& f.severity() == GuiMessageSeverity.ERROR);
|
||||
assertTrue(hasErrorForField,
|
||||
"Value above safe maximum must be an ERROR for this field");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Smoke test: a value in the lower warning band (10..19) produces a field finding that is
|
||||
* not marked as ERROR.
|
||||
*
|
||||
* @throws Exception if the FX thread task fails or times out
|
||||
*/
|
||||
@Test
|
||||
void lowWarnMaxTitleLength_producesWarningOnly() throws Exception {
|
||||
runOnFx(() -> {
|
||||
GuiConfigurationEditorWorkspace ws =
|
||||
new GuiConfigurationEditorWorkspace(Optional.empty());
|
||||
ws.requestNewConfiguration();
|
||||
ws.editorState = ws.editorState().withValues(
|
||||
ws.editorState().values().withMaxTitleLength("15"));
|
||||
ws.validateButton.fire();
|
||||
|
||||
assertTrue(ws.lastValidationResult().hasFieldFindingFor("max.title.length"),
|
||||
"Value in low warn band must produce a field finding");
|
||||
boolean hasErrorForField = ws.lastValidationResult().fieldFindings().stream()
|
||||
.anyMatch(f -> "max.title.length".equals(f.fieldKey())
|
||||
&& f.severity() == GuiMessageSeverity.ERROR);
|
||||
assertFalse(hasErrorForField,
|
||||
"Value in low warn band must not produce an ERROR for this field");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Smoke test: a value in the upper warning band (100..120) produces a field finding that is
|
||||
* not marked as ERROR.
|
||||
*
|
||||
* @throws Exception if the FX thread task fails or times out
|
||||
*/
|
||||
@Test
|
||||
void highWarnMaxTitleLength_producesWarningOnly() throws Exception {
|
||||
runOnFx(() -> {
|
||||
GuiConfigurationEditorWorkspace ws =
|
||||
new GuiConfigurationEditorWorkspace(Optional.empty());
|
||||
ws.requestNewConfiguration();
|
||||
ws.editorState = ws.editorState().withValues(
|
||||
ws.editorState().values().withMaxTitleLength("110"));
|
||||
ws.validateButton.fire();
|
||||
|
||||
assertTrue(ws.lastValidationResult().hasFieldFindingFor("max.title.length"),
|
||||
"Value in high warn band must produce a field finding");
|
||||
boolean hasErrorForField = ws.lastValidationResult().fieldFindings().stream()
|
||||
.anyMatch(f -> "max.title.length".equals(f.fieldKey())
|
||||
&& f.severity() == GuiMessageSeverity.ERROR);
|
||||
assertFalse(hasErrorForField,
|
||||
"Value in high warn band must not produce an ERROR for this field");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Smoke test: the default template value of 60 produces no finding for the title-length field.
|
||||
*
|
||||
* @throws Exception if the FX thread task fails or times out
|
||||
*/
|
||||
@Test
|
||||
void defaultMaxTitleLength_producesNoFieldFinding() throws Exception {
|
||||
runOnFx(() -> {
|
||||
GuiConfigurationEditorWorkspace ws =
|
||||
new GuiConfigurationEditorWorkspace(Optional.empty());
|
||||
ws.requestNewConfiguration();
|
||||
|
||||
assertNotNull(ws.lastValidationResult(),
|
||||
"lastValidationResult must not be null after 'Neu'");
|
||||
assertFalse(ws.lastValidationResult().hasFieldFindingFor("max.title.length"),
|
||||
"Default value 60 must not produce a field finding");
|
||||
});
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Helpers
|
||||
// =========================================================================
|
||||
@@ -458,6 +614,7 @@ class GuiEditorValidationSmokeTest {
|
||||
+ "max.retries.transient=3\n"
|
||||
+ "max.pages=10\n"
|
||||
+ "max.text.characters=500\n"
|
||||
+ "max.title.length=60\n"
|
||||
+ "prompt.template.file=./config/prompt.txt\n";
|
||||
Files.writeString(path, content, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
+3
-3
@@ -246,7 +246,7 @@ class GuiTechnicalTestCoordinatorSmokeTest {
|
||||
new EditorValidationInput(
|
||||
"claude",
|
||||
"/src", "/tgt", "/db.sqlite", "/prompt.txt",
|
||||
"3", "10", "500",
|
||||
"3", "10", "500", "60",
|
||||
"https://api.anthropic.com", "claude-3-sonnet", "30",
|
||||
EffectiveApiKeyDescriptor.absent(), "",
|
||||
"https://api.openai.com", "gpt-4", "30",
|
||||
@@ -282,7 +282,7 @@ class GuiTechnicalTestCoordinatorSmokeTest {
|
||||
currentInput.set(new EditorValidationInput(
|
||||
"", // empty active provider → validation error in block 1
|
||||
"/src", "/tgt", "/db.sqlite", "/prompt.txt",
|
||||
"3", "10", "500",
|
||||
"3", "10", "500", "60",
|
||||
"https://api.anthropic.com", "claude-3-sonnet", "30",
|
||||
EffectiveApiKeyDescriptor.absent(), "",
|
||||
"https://api.openai.com", "gpt-4", "30",
|
||||
@@ -369,7 +369,7 @@ class GuiTechnicalTestCoordinatorSmokeTest {
|
||||
EditorValidationInput blankInput = new EditorValidationInput(
|
||||
"claude",
|
||||
"/src", "/tgt", "/db.sqlite", "/prompt.txt",
|
||||
"3", "10", "2000",
|
||||
"3", "10", "2000", "60",
|
||||
"https://api.anthropic.com", "claude-3-sonnet", "30",
|
||||
EffectiveApiKeyDescriptor.absent(), "",
|
||||
"https://api.openai.com", "gpt-4", "30",
|
||||
|
||||
+1
@@ -874,6 +874,7 @@ class GuiUnsavedChangesGuardSmokeTest {
|
||||
v.maxRetriesTransient(),
|
||||
v.maxPages(),
|
||||
v.maxTextCharacters(),
|
||||
v.maxTitleLength(),
|
||||
v.logAiSensitive(),
|
||||
v.activeProviderFamily(),
|
||||
v.providerConfigurations());
|
||||
|
||||
+1
@@ -166,6 +166,7 @@ class GuiWindowTitleFormatterTest {
|
||||
v.maxRetriesTransient(),
|
||||
v.maxPages(),
|
||||
v.maxTextCharacters(),
|
||||
v.maxTitleLength(),
|
||||
v.logAiSensitive(),
|
||||
v.activeProviderFamily(),
|
||||
v.providerConfigurations());
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ class ConfirmationDialogContentTest {
|
||||
@Test
|
||||
void fromPlan_extractsDescriptionsInOrder() {
|
||||
var s1 = new CorrectionSuggestion.CreateDirectory("/path/a", "Zielordner anlegen");
|
||||
var s2 = new CorrectionSuggestion.CreatePromptFile("/path/prompt.txt", "Prompt-Datei erzeugen");
|
||||
var s2 = new CorrectionSuggestion.CreatePromptFile("/path/prompt.txt", "Prompt-Datei erzeugen", 60);
|
||||
var plan = new CorrectionPlan(List.of(s1, s2));
|
||||
|
||||
var content = ConfirmationDialogContent.fromPlan(plan);
|
||||
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package de.gecheckt.pdf.umbenenner.adapter.in.gui.editor;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests for {@link GuiConfigurationEditorStateFactory}.
|
||||
* <p>
|
||||
* Verifies that loaded properties are correctly mapped into the editor state, with specific
|
||||
* attention to the {@code max.title.length} property mapping.
|
||||
*/
|
||||
class GuiConfigurationEditorStateFactoryTest {
|
||||
|
||||
@Test
|
||||
void fromPropertiesSnapshot_mapsMaxTitleLengthWhenPresent() {
|
||||
Properties props = new Properties();
|
||||
props.setProperty("source.folder", "./s");
|
||||
props.setProperty("target.folder", "./t");
|
||||
props.setProperty("sqlite.file", "./db");
|
||||
props.setProperty("prompt.template.file", "./p.txt");
|
||||
props.setProperty("ai.provider.active", "claude");
|
||||
props.setProperty("max.retries.transient", "3");
|
||||
props.setProperty("max.pages", "10");
|
||||
props.setProperty("max.text.characters", "5000");
|
||||
props.setProperty("max.title.length", "80");
|
||||
GuiConfigurationFileSnapshot snapshot =
|
||||
new GuiConfigurationFileSnapshot(Path.of("config/application.properties"), props);
|
||||
|
||||
GuiConfigurationEditorState state =
|
||||
GuiConfigurationEditorStateFactory.fromPropertiesSnapshot(snapshot, Optional.empty());
|
||||
|
||||
assertEquals("80", state.values().maxTitleLength(),
|
||||
"Loaded max.title.length value must be present in the editor state");
|
||||
assertTrue(state.hasLoadedFileSnapshot());
|
||||
assertFalse(state.isDirty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromPropertiesSnapshot_missingMaxTitleLengthBecomesBlank() {
|
||||
Properties props = new Properties();
|
||||
props.setProperty("source.folder", "./s");
|
||||
props.setProperty("target.folder", "./t");
|
||||
props.setProperty("ai.provider.active", "claude");
|
||||
// max.title.length intentionally omitted
|
||||
GuiConfigurationFileSnapshot snapshot =
|
||||
new GuiConfigurationFileSnapshot(Path.of("config/application.properties"), props);
|
||||
|
||||
GuiConfigurationEditorState state =
|
||||
GuiConfigurationEditorStateFactory.fromPropertiesSnapshot(snapshot, Optional.empty());
|
||||
|
||||
assertEquals("", state.values().maxTitleLength(),
|
||||
"A missing max.title.length property must be mapped to an empty string");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromPropertiesSnapshot_blankMaxTitleLengthBecomesBlank() {
|
||||
Properties props = new Properties();
|
||||
props.setProperty("source.folder", "./s");
|
||||
props.setProperty("target.folder", "./t");
|
||||
props.setProperty("ai.provider.active", "claude");
|
||||
props.setProperty("max.title.length", " ");
|
||||
GuiConfigurationFileSnapshot snapshot =
|
||||
new GuiConfigurationFileSnapshot(Path.of("config/application.properties"), props);
|
||||
|
||||
GuiConfigurationEditorState state =
|
||||
GuiConfigurationEditorStateFactory.fromPropertiesSnapshot(snapshot, Optional.empty());
|
||||
|
||||
assertEquals("", state.values().maxTitleLength(),
|
||||
"A blank max.title.length property must be trimmed to an empty string");
|
||||
}
|
||||
}
|
||||
+2
@@ -24,6 +24,7 @@ class GuiConfigurationEditorStateTest {
|
||||
state.values().maxRetriesTransient(),
|
||||
state.values().maxPages(),
|
||||
state.values().maxTextCharacters(),
|
||||
state.values().maxTitleLength(),
|
||||
"maybe",
|
||||
"claude-42",
|
||||
state.values().providerConfigurations());
|
||||
@@ -90,6 +91,7 @@ class GuiConfigurationEditorStateTest {
|
||||
state.values().maxRetriesTransient(),
|
||||
state.values().maxPages(),
|
||||
state.values().maxTextCharacters(),
|
||||
state.values().maxTitleLength(),
|
||||
"true",
|
||||
"openai-compatible",
|
||||
state.values().providerConfigurations());
|
||||
|
||||
+4
@@ -34,6 +34,7 @@ class GuiConfigurationTemplateFactoryTest {
|
||||
assertEquals("3", values.maxRetriesTransient());
|
||||
assertEquals("10", values.maxPages());
|
||||
assertEquals("5000", values.maxTextCharacters());
|
||||
assertEquals("60", values.maxTitleLength());
|
||||
assertEquals("false", values.logAiSensitive());
|
||||
assertEquals(AiProviderFamily.CLAUDE.getIdentifier(), values.activeProviderFamily());
|
||||
|
||||
@@ -69,6 +70,8 @@ class GuiConfigurationTemplateFactoryTest {
|
||||
GuiConfigurationValues values = state.values();
|
||||
assertEquals("./work/local/source", values.sourceFolder());
|
||||
assertEquals("./work/local/target", values.targetFolder());
|
||||
assertEquals("60", values.maxTitleLength(),
|
||||
"Standard template must supply the default title-length value");
|
||||
assertEquals(AiProviderFamily.CLAUDE.getIdentifier(), values.activeProviderFamily());
|
||||
assertFalse(values.providerConfigurations().isEmpty());
|
||||
}
|
||||
@@ -93,6 +96,7 @@ class GuiConfigurationTemplateFactoryTest {
|
||||
assertEquals("", values.maxRetriesTransient());
|
||||
assertEquals("", values.maxPages());
|
||||
assertEquals("", values.maxTextCharacters());
|
||||
assertEquals("", values.maxTitleLength());
|
||||
assertEquals("", values.logAiSensitive());
|
||||
assertEquals("", values.activeProviderFamily());
|
||||
assertTrue(values.providerConfigurations().isEmpty());
|
||||
|
||||
+57
@@ -28,10 +28,12 @@ class GuiConfigurationValuesTest {
|
||||
"12",
|
||||
"34",
|
||||
"56",
|
||||
"78",
|
||||
"maybe",
|
||||
"not-a-provider-family",
|
||||
providerConfigurations);
|
||||
|
||||
assertEquals("78", values.maxTitleLength());
|
||||
assertEquals("maybe", values.logAiSensitive());
|
||||
assertEquals("not-a-provider-family", values.activeProviderFamily());
|
||||
assertEquals(GuiProviderConfigurationState.blank(), values.providerConfiguration(AiProviderFamily.CLAUDE));
|
||||
@@ -53,6 +55,7 @@ class GuiConfigurationValuesTest {
|
||||
"12",
|
||||
"34",
|
||||
"56",
|
||||
"60",
|
||||
"true",
|
||||
AiProviderFamily.CLAUDE.getIdentifier(),
|
||||
providerConfigurations);
|
||||
@@ -62,4 +65,58 @@ class GuiConfigurationValuesTest {
|
||||
assertNotSame(providerConfigurations, values.providerConfigurations());
|
||||
assertEquals(1, values.providerConfigurations().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void withMaxTitleLength_producesIndependentCopy() {
|
||||
Map<AiProviderFamily, GuiProviderConfigurationState> providerConfigurations = new LinkedHashMap<>();
|
||||
providerConfigurations.put(AiProviderFamily.CLAUDE, GuiProviderConfigurationState.blank());
|
||||
|
||||
GuiConfigurationValues original = new GuiConfigurationValues(
|
||||
"./source",
|
||||
"./target",
|
||||
"./config/db.sqlite",
|
||||
"./config/prompt.txt",
|
||||
"./config/runtime.lock",
|
||||
"./logs",
|
||||
"INFO",
|
||||
"3",
|
||||
"10",
|
||||
"5000",
|
||||
"60",
|
||||
"false",
|
||||
AiProviderFamily.CLAUDE.getIdentifier(),
|
||||
providerConfigurations);
|
||||
|
||||
GuiConfigurationValues updated = original.withMaxTitleLength("80");
|
||||
|
||||
assertEquals("80", updated.maxTitleLength());
|
||||
assertEquals("60", original.maxTitleLength(),
|
||||
"Original instance must remain unchanged");
|
||||
assertEquals(original.sourceFolder(), updated.sourceFolder(),
|
||||
"Unrelated fields must be preserved when copying");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullMaxTitleLengthBecomesEmptyString() {
|
||||
Map<AiProviderFamily, GuiProviderConfigurationState> providerConfigurations = new LinkedHashMap<>();
|
||||
providerConfigurations.put(AiProviderFamily.CLAUDE, GuiProviderConfigurationState.blank());
|
||||
|
||||
GuiConfigurationValues values = new GuiConfigurationValues(
|
||||
"./source",
|
||||
"./target",
|
||||
"./config/db.sqlite",
|
||||
"./config/prompt.txt",
|
||||
"./config/runtime.lock",
|
||||
"./logs",
|
||||
"INFO",
|
||||
"3",
|
||||
"10",
|
||||
"5000",
|
||||
null,
|
||||
"false",
|
||||
AiProviderFamily.CLAUDE.getIdentifier(),
|
||||
providerConfigurations);
|
||||
|
||||
assertEquals("", values.maxTitleLength());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user