Umsetzung von Meilenstein M7
This commit is contained in:
@@ -227,8 +227,8 @@ public class StartConfigurationValidator {
|
||||
}
|
||||
|
||||
private void validateMaxRetriesTransient(int maxRetriesTransient, List<String> errors) {
|
||||
if (maxRetriesTransient < 0) {
|
||||
errors.add("- max.retries.transient: must be >= 0, got: " + maxRetriesTransient);
|
||||
if (maxRetriesTransient < 1) {
|
||||
errors.add("- max.retries.transient: must be >= 1, got: " + maxRetriesTransient);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -108,6 +108,7 @@ public class PropertiesConfigurationPortAdapter implements ConfigurationPort {
|
||||
}
|
||||
|
||||
private StartConfiguration buildStartConfiguration(Properties props, String apiKey) {
|
||||
boolean logAiSensitive = Boolean.parseBoolean(getOptionalProperty(props, "log.ai.sensitive", "false"));
|
||||
return new StartConfiguration(
|
||||
Paths.get(getRequiredProperty(props, "source.folder")),
|
||||
Paths.get(getRequiredProperty(props, "target.folder")),
|
||||
@@ -122,7 +123,8 @@ public class PropertiesConfigurationPortAdapter implements ConfigurationPort {
|
||||
Paths.get(getOptionalProperty(props, "runtime.lock.file", "")),
|
||||
Paths.get(getOptionalProperty(props, "log.directory", "")),
|
||||
getOptionalProperty(props, "log.level", "INFO"),
|
||||
apiKey
|
||||
apiKey,
|
||||
logAiSensitive
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,8 @@ class OpenAiHttpAdapterTest {
|
||||
Paths.get("/lock"),
|
||||
Paths.get("/logs"),
|
||||
"INFO",
|
||||
API_KEY
|
||||
API_KEY,
|
||||
false
|
||||
);
|
||||
// Use the package-private constructor with injected mock HttpClient
|
||||
adapter = new OpenAiHttpAdapter(testConfiguration, httpClient);
|
||||
@@ -450,7 +451,8 @@ class OpenAiHttpAdapterTest {
|
||||
Paths.get("/lock"),
|
||||
Paths.get("/logs"),
|
||||
"INFO",
|
||||
API_KEY
|
||||
API_KEY,
|
||||
false
|
||||
);
|
||||
|
||||
assertThatThrownBy(() -> new OpenAiHttpAdapter(invalidConfig, httpClient))
|
||||
@@ -475,7 +477,8 @@ class OpenAiHttpAdapterTest {
|
||||
Paths.get("/lock"),
|
||||
Paths.get("/logs"),
|
||||
"INFO",
|
||||
API_KEY
|
||||
API_KEY,
|
||||
false
|
||||
);
|
||||
|
||||
assertThatThrownBy(() -> new OpenAiHttpAdapter(invalidConfig, httpClient))
|
||||
@@ -500,7 +503,8 @@ class OpenAiHttpAdapterTest {
|
||||
Paths.get("/lock"),
|
||||
Paths.get("/logs"),
|
||||
"INFO",
|
||||
API_KEY
|
||||
API_KEY,
|
||||
false
|
||||
);
|
||||
|
||||
assertThatThrownBy(() -> new OpenAiHttpAdapter(invalidConfig, httpClient))
|
||||
@@ -526,7 +530,8 @@ class OpenAiHttpAdapterTest {
|
||||
Paths.get("/lock"),
|
||||
Paths.get("/logs"),
|
||||
"INFO",
|
||||
"" // Empty key
|
||||
"", // Empty key
|
||||
false
|
||||
);
|
||||
|
||||
OpenAiHttpAdapter adapterWithEmptyKey = new OpenAiHttpAdapter(configWithEmptyKey, httpClient);
|
||||
|
||||
@@ -44,7 +44,8 @@ class StartConfigurationValidatorTest {
|
||||
tempDir.resolve("lock.lock"),
|
||||
tempDir.resolve("logs"),
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
assertDoesNotThrow(() -> validator.validate(config));
|
||||
@@ -66,7 +67,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -92,7 +94,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -118,7 +121,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -149,7 +153,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -180,7 +185,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -210,7 +216,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -241,7 +248,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -272,14 +280,48 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
InvalidStartConfigurationException.class,
|
||||
() -> validator.validate(config)
|
||||
);
|
||||
assertTrue(exception.getMessage().contains("max.retries.transient: must be >= 0"));
|
||||
assertTrue(exception.getMessage().contains("max.retries.transient: must be >= 1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_failsWhenMaxRetriesTransientIsZero() throws Exception {
|
||||
Path sourceFolder = Files.createDirectory(tempDir.resolve("source2"));
|
||||
Path targetFolder = Files.createDirectory(tempDir.resolve("target2"));
|
||||
Path sqliteFile = Files.createFile(tempDir.resolve("db2.sqlite"));
|
||||
Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt2.txt"));
|
||||
|
||||
StartConfiguration config = new StartConfiguration(
|
||||
sourceFolder,
|
||||
targetFolder,
|
||||
sqliteFile,
|
||||
URI.create("https://api.example.com"),
|
||||
"gpt-4",
|
||||
30,
|
||||
0,
|
||||
100,
|
||||
50000,
|
||||
promptTemplateFile,
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
InvalidStartConfigurationException.class,
|
||||
() -> validator.validate(config)
|
||||
);
|
||||
assertTrue(exception.getMessage().contains("max.retries.transient: must be >= 1"),
|
||||
"max.retries.transient = 0 is invalid startup configuration");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -303,7 +345,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -334,7 +377,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -345,7 +389,7 @@ class StartConfigurationValidatorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_maxRetriesTransientZeroIsValid() throws Exception {
|
||||
void validate_maxRetriesTransientOneIsValid() throws Exception {
|
||||
Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
|
||||
Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
|
||||
Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
|
||||
@@ -358,14 +402,15 @@ class StartConfigurationValidatorTest {
|
||||
URI.create("https://api.example.com"),
|
||||
"gpt-4",
|
||||
30,
|
||||
0, // maxRetriesTransient = 0 ist gültig
|
||||
1, // maxRetriesTransient = 1 is the minimum valid value
|
||||
100,
|
||||
50000,
|
||||
promptTemplateFile,
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
assertDoesNotThrow(() -> validator.validate(config));
|
||||
@@ -392,7 +437,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -422,7 +468,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -453,7 +500,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -485,7 +533,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
assertDoesNotThrow(() -> validator.validate(config),
|
||||
@@ -516,7 +565,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -548,7 +598,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -578,7 +629,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -609,7 +661,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -640,7 +693,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -670,7 +724,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -701,7 +756,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -731,7 +787,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -757,7 +814,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -772,7 +830,7 @@ class StartConfigurationValidatorTest {
|
||||
assertTrue(message.contains("api.model: must not be null or blank"));
|
||||
assertTrue(message.contains("prompt.template.file: must not be null"));
|
||||
assertTrue(message.contains("api.timeoutSeconds: must be > 0"));
|
||||
assertTrue(message.contains("max.retries.transient: must be >= 0"));
|
||||
assertTrue(message.contains("max.retries.transient: must be >= 1"));
|
||||
assertTrue(message.contains("max.pages: must be > 0"));
|
||||
assertTrue(message.contains("max.text.characters: must be > 0"));
|
||||
}
|
||||
@@ -804,7 +862,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
// Mock: always return "does not exist" error for any path
|
||||
@@ -840,7 +899,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
// Mock: simulate path exists but is not a directory
|
||||
@@ -876,7 +936,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
// Mock: simulate path exists, is directory, but is not readable
|
||||
@@ -914,7 +975,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
// Mock: all checks pass (return null)
|
||||
@@ -951,7 +1013,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -985,7 +1048,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -1016,7 +1080,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -1047,7 +1112,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -1078,7 +1144,8 @@ class StartConfigurationValidatorTest {
|
||||
tempDir.resolve("nonexistent/lock.lock"), // Lock file mit nicht existierendem Parent
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -1113,7 +1180,8 @@ class StartConfigurationValidatorTest {
|
||||
lockFileWithFileAsParent, // Lock file mit Datei als Parent
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -1147,7 +1215,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
logFileInsteadOfDirectory, // Datei statt Verzeichnis
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
@@ -1178,7 +1247,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
assertDoesNotThrow(() -> validator.validate(config),
|
||||
@@ -1206,7 +1276,8 @@ class StartConfigurationValidatorTest {
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
"test-api-key",
|
||||
false
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.gecheckt.pdf.umbenenner.adapter.out.configuration;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
@@ -328,6 +329,56 @@ class PropertiesConfigurationPortAdapterTest {
|
||||
"Invalid URI value should throw ConfigurationLoadingException");
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadConfiguration_logAiSensitiveDefaultsFalseWhenAbsent() throws Exception {
|
||||
Path configFile = createInlineConfig(
|
||||
"source.folder=/tmp/source\n" +
|
||||
"target.folder=/tmp/target\n" +
|
||||
"sqlite.file=/tmp/db.sqlite\n" +
|
||||
"api.baseUrl=https://api.example.com\n" +
|
||||
"api.model=gpt-4\n" +
|
||||
"api.timeoutSeconds=30\n" +
|
||||
"max.retries.transient=3\n" +
|
||||
"max.pages=100\n" +
|
||||
"max.text.characters=50000\n" +
|
||||
"prompt.template.file=/tmp/prompt.txt\n" +
|
||||
"api.key=test-key\n"
|
||||
// log.ai.sensitive intentionally omitted
|
||||
);
|
||||
|
||||
PropertiesConfigurationPortAdapter adapter = new PropertiesConfigurationPortAdapter(emptyEnvLookup, configFile);
|
||||
|
||||
var config = adapter.loadConfiguration();
|
||||
|
||||
assertFalse(config.logAiSensitive(),
|
||||
"log.ai.sensitive must default to false when the property is absent");
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadConfiguration_logAiSensitiveParsedTrueWhenExplicitlySet() throws Exception {
|
||||
Path configFile = createInlineConfig(
|
||||
"source.folder=/tmp/source\n" +
|
||||
"target.folder=/tmp/target\n" +
|
||||
"sqlite.file=/tmp/db.sqlite\n" +
|
||||
"api.baseUrl=https://api.example.com\n" +
|
||||
"api.model=gpt-4\n" +
|
||||
"api.timeoutSeconds=30\n" +
|
||||
"max.retries.transient=3\n" +
|
||||
"max.pages=100\n" +
|
||||
"max.text.characters=50000\n" +
|
||||
"prompt.template.file=/tmp/prompt.txt\n" +
|
||||
"api.key=test-key\n" +
|
||||
"log.ai.sensitive=true\n"
|
||||
);
|
||||
|
||||
PropertiesConfigurationPortAdapter adapter = new PropertiesConfigurationPortAdapter(emptyEnvLookup, configFile);
|
||||
|
||||
var config = adapter.loadConfiguration();
|
||||
|
||||
assertTrue(config.logAiSensitive(),
|
||||
"log.ai.sensitive must be parsed as true when explicitly set to 'true'");
|
||||
}
|
||||
|
||||
private Path createConfigFile(String resourceName) throws Exception {
|
||||
Path sourceResource = Path.of("src/test/resources", resourceName);
|
||||
Path targetConfigFile = tempDir.resolve("application.properties");
|
||||
|
||||
Reference in New Issue
Block a user