M1 Vollständiger Grundstand mit Build, Konfiguration, Tests und Smoke-Tests
This commit is contained in:
43
pdf-umbenenner-application/pom.xml
Normal file
43
pdf-umbenenner-application/pom.xml
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>de.gecheckt</groupId>
|
||||
<artifactId>pdf-umbenenner-parent</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>pdf-umbenenner-application</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<!-- Internal dependency -->
|
||||
<dependency>
|
||||
<groupId>de.gecheckt</groupId>
|
||||
<artifactId>pdf-umbenenner-domain</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Logging -->
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1 @@
|
||||
# Keep directory
|
||||
@@ -0,0 +1,21 @@
|
||||
package de.gecheckt.pdf.umbenenner.application.config;
|
||||
|
||||
/**
|
||||
* Exception thrown when startup configuration validation fails.
|
||||
* <p>
|
||||
* Contains an aggregated message describing all validation errors found.
|
||||
* This is a controlled failure mode that prevents processing from starting.
|
||||
*/
|
||||
public class InvalidStartConfigurationException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Creates the exception with an aggregated error message.
|
||||
*
|
||||
* @param message the aggregated validation error message
|
||||
*/
|
||||
public InvalidStartConfigurationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package de.gecheckt.pdf.umbenenner.application.config;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Typed immutable configuration model for PDF Umbenenner startup parameters.
|
||||
* AP-005: Represents all M1-relevant configuration properties with strong typing.
|
||||
*/
|
||||
public record StartConfiguration(
|
||||
Path sourceFolder,
|
||||
Path targetFolder,
|
||||
Path sqliteFile,
|
||||
URI apiBaseUrl,
|
||||
String apiModel,
|
||||
int apiTimeoutSeconds,
|
||||
int maxRetriesTransient,
|
||||
int maxPages,
|
||||
int maxTextCharacters,
|
||||
Path promptTemplateFile,
|
||||
Path runtimeLockFile,
|
||||
Path logDirectory,
|
||||
String logLevel,
|
||||
String apiKey
|
||||
)
|
||||
{ }
|
||||
@@ -0,0 +1,198 @@
|
||||
package de.gecheckt.pdf.umbenenner.application.config;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Validates {@link StartConfiguration} before processing can begin.
|
||||
* <p>
|
||||
* Performs mandatory field checks, numeric range validation, URI scheme validation,
|
||||
* and basic path existence checks. Throws {@link InvalidStartConfigurationException}
|
||||
* if any validation rule fails.
|
||||
*/
|
||||
public class StartConfigurationValidator {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(StartConfigurationValidator.class);
|
||||
|
||||
/**
|
||||
* Validates the given configuration.
|
||||
* <p>
|
||||
* Checks all mandatory fields, numeric constraints, URI validity, and path existence.
|
||||
* If validation fails, throws {@link InvalidStartConfigurationException} with an
|
||||
* aggregated error message listing all problems.
|
||||
*
|
||||
* @param config the configuration to validate, must not be null
|
||||
* @throws InvalidStartConfigurationException if any validation rule fails
|
||||
*/
|
||||
public void validate(StartConfiguration config) {
|
||||
List<String> errors = new ArrayList<>();
|
||||
|
||||
// Mandatory string/path presence checks
|
||||
validateSourceFolder(config.sourceFolder(), errors);
|
||||
validateTargetFolder(config.targetFolder(), errors);
|
||||
validateSqliteFile(config.sqliteFile(), errors);
|
||||
validateApiBaseUrl(config.apiBaseUrl(), errors);
|
||||
validateApiModel(config.apiModel(), errors);
|
||||
validatePromptTemplateFile(config.promptTemplateFile(), errors);
|
||||
|
||||
// Numeric validation
|
||||
validateApiTimeoutSeconds(config.apiTimeoutSeconds(), errors);
|
||||
validateMaxRetriesTransient(config.maxRetriesTransient(), errors);
|
||||
validateMaxPages(config.maxPages(), errors);
|
||||
validateMaxTextCharacters(config.maxTextCharacters(), errors);
|
||||
|
||||
// Path relationship validation
|
||||
validateSourceAndTargetNotSame(config.sourceFolder(), config.targetFolder(), errors);
|
||||
|
||||
// Optional path validations (only if present)
|
||||
validateRuntimeLockFile(config.runtimeLockFile(), errors);
|
||||
validateLogDirectory(config.logDirectory(), errors);
|
||||
|
||||
if (!errors.isEmpty()) {
|
||||
String errorMessage = "Invalid startup configuration:\n" + String.join("\n", errors);
|
||||
throw new InvalidStartConfigurationException(errorMessage);
|
||||
}
|
||||
|
||||
LOG.info("Configuration validation successful.");
|
||||
}
|
||||
|
||||
private void validateSourceFolder(Path sourceFolder, List<String> errors) {
|
||||
if (sourceFolder == null) {
|
||||
errors.add("- source.folder: must not be null");
|
||||
return;
|
||||
}
|
||||
if (!Files.exists(sourceFolder)) {
|
||||
errors.add("- source.folder: path does not exist: " + sourceFolder);
|
||||
} else if (!Files.isDirectory(sourceFolder)) {
|
||||
errors.add("- source.folder: path is not a directory: " + sourceFolder);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateTargetFolder(Path targetFolder, List<String> errors) {
|
||||
if (targetFolder == null) {
|
||||
errors.add("- target.folder: must not be null");
|
||||
return;
|
||||
}
|
||||
if (!Files.exists(targetFolder)) {
|
||||
errors.add("- target.folder: path does not exist: " + targetFolder);
|
||||
} else if (!Files.isDirectory(targetFolder)) {
|
||||
errors.add("- target.folder: path is not a directory: " + targetFolder);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateSqliteFile(Path sqliteFile, List<String> errors) {
|
||||
if (sqliteFile == null) {
|
||||
errors.add("- sqlite.file: must not be null");
|
||||
return;
|
||||
}
|
||||
Path parent = sqliteFile.getParent();
|
||||
if (parent == null) {
|
||||
errors.add("- sqlite.file: has no parent directory: " + sqliteFile);
|
||||
} else if (!Files.exists(parent)) {
|
||||
errors.add("- sqlite.file: parent directory does not exist: " + parent);
|
||||
} else if (!Files.isDirectory(parent)) {
|
||||
errors.add("- sqlite.file: parent is not a directory: " + parent);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateApiBaseUrl(java.net.URI apiBaseUrl, List<String> errors) {
|
||||
if (apiBaseUrl == null) {
|
||||
errors.add("- api.baseUrl: must not be null");
|
||||
return;
|
||||
}
|
||||
if (!apiBaseUrl.isAbsolute()) {
|
||||
errors.add("- api.baseUrl: must be an absolute URI: " + apiBaseUrl);
|
||||
return;
|
||||
}
|
||||
String scheme = apiBaseUrl.getScheme();
|
||||
if (scheme == null || (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme))) {
|
||||
errors.add("- api.baseUrl: scheme must be http or https, got: " + scheme);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateApiModel(String apiModel, List<String> errors) {
|
||||
if (apiModel == null || apiModel.isBlank()) {
|
||||
errors.add("- api.model: must not be null or blank");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateApiTimeoutSeconds(int apiTimeoutSeconds, List<String> errors) {
|
||||
if (apiTimeoutSeconds <= 0) {
|
||||
errors.add("- api.timeoutSeconds: must be > 0, got: " + apiTimeoutSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateMaxRetriesTransient(int maxRetriesTransient, List<String> errors) {
|
||||
if (maxRetriesTransient < 0) {
|
||||
errors.add("- max.retries.transient: must be >= 0, got: " + maxRetriesTransient);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateMaxPages(int maxPages, List<String> errors) {
|
||||
if (maxPages <= 0) {
|
||||
errors.add("- max.pages: must be > 0, got: " + maxPages);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateMaxTextCharacters(int maxTextCharacters, List<String> errors) {
|
||||
if (maxTextCharacters <= 0) {
|
||||
errors.add("- max.text.characters: must be > 0, got: " + maxTextCharacters);
|
||||
}
|
||||
}
|
||||
|
||||
private void validatePromptTemplateFile(Path promptTemplateFile, List<String> errors) {
|
||||
if (promptTemplateFile == null) {
|
||||
errors.add("- prompt.template.file: must not be null");
|
||||
return;
|
||||
}
|
||||
if (!Files.exists(promptTemplateFile)) {
|
||||
errors.add("- prompt.template.file: path does not exist: " + promptTemplateFile);
|
||||
} else if (!Files.isRegularFile(promptTemplateFile)) {
|
||||
errors.add("- prompt.template.file: path is not a regular file: " + promptTemplateFile);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateSourceAndTargetNotSame(Path sourceFolder, Path targetFolder, List<String> errors) {
|
||||
if (sourceFolder != null && targetFolder != null) {
|
||||
try {
|
||||
Path normalizedSource = sourceFolder.toRealPath();
|
||||
Path normalizedTarget = targetFolder.toRealPath();
|
||||
if (normalizedSource.equals(normalizedTarget)) {
|
||||
errors.add("- source.folder and target.folder must not resolve to the same path: " + normalizedSource);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// If toRealPath fails (e.g., path doesn't exist), skip this check
|
||||
// The individual existence checks will catch missing paths
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateRuntimeLockFile(Path runtimeLockFile, List<String> errors) {
|
||||
if (runtimeLockFile != null && !runtimeLockFile.toString().isBlank()) {
|
||||
Path parent = runtimeLockFile.getParent();
|
||||
if (parent != null) {
|
||||
if (!Files.exists(parent)) {
|
||||
errors.add("- runtime.lock.file: parent directory does not exist: " + parent);
|
||||
} else if (!Files.isDirectory(parent)) {
|
||||
errors.add("- runtime.lock.file: parent is not a directory: " + parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateLogDirectory(Path logDirectory, List<String> errors) {
|
||||
if (logDirectory != null && !logDirectory.toString().isBlank()) {
|
||||
if (Files.exists(logDirectory)) {
|
||||
if (!Files.isDirectory(logDirectory)) {
|
||||
errors.add("- log.directory: exists but is not a directory: " + logDirectory);
|
||||
}
|
||||
}
|
||||
// If it doesn't exist yet, that's acceptable - we don't auto-create
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Configuration model types for the PDF Umbenenner application.
|
||||
* Contains typed configuration objects representing startup parameters.
|
||||
*/
|
||||
package de.gecheckt.pdf.umbenenner.application.config;
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Application layer containing use cases and inbound ports.
|
||||
* This package defines the business logic boundary without infrastructure details.
|
||||
*/
|
||||
package de.gecheckt.pdf.umbenenner.application;
|
||||
@@ -0,0 +1,18 @@
|
||||
package de.gecheckt.pdf.umbenenner.application.port.in;
|
||||
|
||||
/**
|
||||
* Inbound port for batch processing execution.
|
||||
* This interface defines the contract for triggering batch operations.
|
||||
* <p>
|
||||
* AP-003 Implementation: Currently a no-op placeholder to establish the technical startup path.
|
||||
*/
|
||||
public interface RunBatchProcessingUseCase {
|
||||
/**
|
||||
* Executes the batch processing workflow.
|
||||
* <p>
|
||||
* AP-003: This method performs no actual work, only validates the call chain.
|
||||
*
|
||||
* @return true if the workflow completed successfully, false otherwise
|
||||
*/
|
||||
boolean execute();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Inbound ports (application service interfaces) for the hexagonal architecture.
|
||||
* Use cases in this package are invoked by adapters from the outside world.
|
||||
*/
|
||||
package de.gecheckt.pdf.umbenenner.application.port.in;
|
||||
@@ -0,0 +1,17 @@
|
||||
package de.gecheckt.pdf.umbenenner.application.port.out;
|
||||
|
||||
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
|
||||
|
||||
/**
|
||||
* Outbound port for configuration access.
|
||||
* AP-005: Minimal interface for loading typed startup configuration.
|
||||
*/
|
||||
public interface ConfigurationPort {
|
||||
|
||||
/**
|
||||
* Loads and returns the startup configuration.
|
||||
*
|
||||
* @return the loaded StartConfiguration
|
||||
*/
|
||||
StartConfiguration loadConfiguration();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Outbound ports for the application layer.
|
||||
* Defines interfaces for infrastructure access from the application layer.
|
||||
*/
|
||||
package de.gecheckt.pdf.umbenenner.application.port.out;
|
||||
@@ -0,0 +1,41 @@
|
||||
package de.gecheckt.pdf.umbenenner.application.usecase;
|
||||
|
||||
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.in.RunBatchProcessingUseCase;
|
||||
import de.gecheckt.pdf.umbenenner.application.port.out.ConfigurationPort;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Minimal no-op implementation of {@link RunBatchProcessingUseCase}.
|
||||
* <p>
|
||||
* AP-003 Implementation: Provides a controlled, non-functional startup path
|
||||
* without any business logic, PDF processing, or infrastructure access.
|
||||
* <p>
|
||||
* AP-005: Accepts {@link ConfigurationPort} to load typed startup configuration.
|
||||
*/
|
||||
public class NoOpRunBatchProcessingUseCase implements RunBatchProcessingUseCase {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(NoOpRunBatchProcessingUseCase.class);
|
||||
private final ConfigurationPort configurationPort;
|
||||
|
||||
/**
|
||||
* Creates the no-op use case with a configuration port.
|
||||
*
|
||||
* @param configurationPort the configuration port for loading startup configuration
|
||||
*/
|
||||
public NoOpRunBatchProcessingUseCase(ConfigurationPort configurationPort) {
|
||||
this.configurationPort = configurationPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute() {
|
||||
// AP-005: Load configuration through the port (technical loading only)
|
||||
StartConfiguration config = configurationPort.loadConfiguration();
|
||||
LOG.info("Configuration loaded successfully. Source: {}, Target: {}", config.sourceFolder(), config.targetFolder());
|
||||
|
||||
// AP-003: Intentional no-op - validates the technical call chain only
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Use case implementations that realize the inbound port contracts.
|
||||
* Currently contains minimal no-op implementations for AP-003 technical validation.
|
||||
*/
|
||||
package de.gecheckt.pdf.umbenenner.application.usecase;
|
||||
1
pdf-umbenenner-application/src/test/java/.gitkeep
Normal file
1
pdf-umbenenner-application/src/test/java/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
# Keep directory
|
||||
@@ -0,0 +1,686 @@
|
||||
package de.gecheckt.pdf.umbenenner.application.config;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link StartConfigurationValidator}.
|
||||
* <p>
|
||||
* Tests cover valid configuration, missing mandatory fields, invalid values,
|
||||
* and path relationship validations.
|
||||
*/
|
||||
class StartConfigurationValidatorTest {
|
||||
|
||||
private final StartConfigurationValidator validator = new StartConfigurationValidator();
|
||||
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
|
||||
@Test
|
||||
void validate_successWithValidConfiguration() throws Exception {
|
||||
Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
|
||||
Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
|
||||
Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
|
||||
Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));
|
||||
|
||||
StartConfiguration config = new StartConfiguration(
|
||||
sourceFolder,
|
||||
targetFolder,
|
||||
sqliteFile,
|
||||
URI.create("https://api.example.com"),
|
||||
"gpt-4",
|
||||
30,
|
||||
3,
|
||||
100,
|
||||
50000,
|
||||
promptTemplateFile,
|
||||
tempDir.resolve("lock.lock"),
|
||||
tempDir.resolve("logs"),
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
);
|
||||
|
||||
assertDoesNotThrow(() -> validator.validate(config));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_failsWhenSourceFolderIsNull() {
|
||||
StartConfiguration config = new StartConfiguration(
|
||||
null,
|
||||
tempDir.resolve("target"),
|
||||
tempDir.resolve("db.sqlite"),
|
||||
URI.create("https://api.example.com"),
|
||||
"gpt-4",
|
||||
30,
|
||||
3,
|
||||
100,
|
||||
50000,
|
||||
tempDir.resolve("prompt.txt"),
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
InvalidStartConfigurationException.class,
|
||||
() -> validator.validate(config)
|
||||
);
|
||||
assertTrue(exception.getMessage().contains("source.folder: must not be null"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_failsWhenTargetFolderIsNull() {
|
||||
StartConfiguration config = new StartConfiguration(
|
||||
tempDir.resolve("source"),
|
||||
null,
|
||||
tempDir.resolve("db.sqlite"),
|
||||
URI.create("https://api.example.com"),
|
||||
"gpt-4",
|
||||
30,
|
||||
3,
|
||||
100,
|
||||
50000,
|
||||
tempDir.resolve("prompt.txt"),
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
InvalidStartConfigurationException.class,
|
||||
() -> validator.validate(config)
|
||||
);
|
||||
assertTrue(exception.getMessage().contains("target.folder: must not be null"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_failsWhenSqliteFileIsNull() {
|
||||
StartConfiguration config = new StartConfiguration(
|
||||
tempDir.resolve("source"),
|
||||
tempDir.resolve("target"),
|
||||
null,
|
||||
URI.create("https://api.example.com"),
|
||||
"gpt-4",
|
||||
30,
|
||||
3,
|
||||
100,
|
||||
50000,
|
||||
tempDir.resolve("prompt.txt"),
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
InvalidStartConfigurationException.class,
|
||||
() -> validator.validate(config)
|
||||
);
|
||||
assertTrue(exception.getMessage().contains("sqlite.file: must not be null"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_failsWhenApiBaseUrlIsNull() throws Exception {
|
||||
Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
|
||||
Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
|
||||
Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
|
||||
Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));
|
||||
|
||||
StartConfiguration config = new StartConfiguration(
|
||||
sourceFolder,
|
||||
targetFolder,
|
||||
sqliteFile,
|
||||
null,
|
||||
"gpt-4",
|
||||
30,
|
||||
3,
|
||||
100,
|
||||
50000,
|
||||
promptTemplateFile,
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
InvalidStartConfigurationException.class,
|
||||
() -> validator.validate(config)
|
||||
);
|
||||
assertTrue(exception.getMessage().contains("api.baseUrl: must not be null"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_failsWhenApiModelIsNull() throws Exception {
|
||||
Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
|
||||
Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
|
||||
Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
|
||||
Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));
|
||||
|
||||
StartConfiguration config = new StartConfiguration(
|
||||
sourceFolder,
|
||||
targetFolder,
|
||||
sqliteFile,
|
||||
URI.create("https://api.example.com"),
|
||||
null,
|
||||
30,
|
||||
3,
|
||||
100,
|
||||
50000,
|
||||
promptTemplateFile,
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
InvalidStartConfigurationException.class,
|
||||
() -> validator.validate(config)
|
||||
);
|
||||
assertTrue(exception.getMessage().contains("api.model: must not be null or blank"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_failsWhenPromptTemplateFileIsNull() throws Exception {
|
||||
Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
|
||||
Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
|
||||
Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
|
||||
|
||||
StartConfiguration config = new StartConfiguration(
|
||||
sourceFolder,
|
||||
targetFolder,
|
||||
sqliteFile,
|
||||
URI.create("https://api.example.com"),
|
||||
"gpt-4",
|
||||
30,
|
||||
3,
|
||||
100,
|
||||
50000,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
InvalidStartConfigurationException.class,
|
||||
() -> validator.validate(config)
|
||||
);
|
||||
assertTrue(exception.getMessage().contains("prompt.template.file: must not be null"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_failsWhenApiTimeoutSecondsIsZeroOrNegative() throws Exception {
|
||||
Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
|
||||
Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
|
||||
Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
|
||||
Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));
|
||||
|
||||
StartConfiguration config = new StartConfiguration(
|
||||
sourceFolder,
|
||||
targetFolder,
|
||||
sqliteFile,
|
||||
URI.create("https://api.example.com"),
|
||||
"gpt-4",
|
||||
0,
|
||||
3,
|
||||
100,
|
||||
50000,
|
||||
promptTemplateFile,
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
InvalidStartConfigurationException.class,
|
||||
() -> validator.validate(config)
|
||||
);
|
||||
assertTrue(exception.getMessage().contains("api.timeoutSeconds: must be > 0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_failsWhenMaxRetriesTransientIsNegative() throws Exception {
|
||||
Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
|
||||
Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
|
||||
Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
|
||||
Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));
|
||||
|
||||
StartConfiguration config = new StartConfiguration(
|
||||
sourceFolder,
|
||||
targetFolder,
|
||||
sqliteFile,
|
||||
URI.create("https://api.example.com"),
|
||||
"gpt-4",
|
||||
30,
|
||||
-1,
|
||||
100,
|
||||
50000,
|
||||
promptTemplateFile,
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
InvalidStartConfigurationException.class,
|
||||
() -> validator.validate(config)
|
||||
);
|
||||
assertTrue(exception.getMessage().contains("max.retries.transient: must be >= 0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_failsWhenMaxPagesIsZeroOrNegative() throws Exception {
|
||||
Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
|
||||
Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
|
||||
Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
|
||||
Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));
|
||||
|
||||
StartConfiguration config = new StartConfiguration(
|
||||
sourceFolder,
|
||||
targetFolder,
|
||||
sqliteFile,
|
||||
URI.create("https://api.example.com"),
|
||||
"gpt-4",
|
||||
30,
|
||||
3,
|
||||
0,
|
||||
50000,
|
||||
promptTemplateFile,
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
InvalidStartConfigurationException.class,
|
||||
() -> validator.validate(config)
|
||||
);
|
||||
assertTrue(exception.getMessage().contains("max.pages: must be > 0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_failsWhenMaxTextCharactersIsZeroOrNegative() throws Exception {
|
||||
Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
|
||||
Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
|
||||
Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
|
||||
Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));
|
||||
|
||||
StartConfiguration config = new StartConfiguration(
|
||||
sourceFolder,
|
||||
targetFolder,
|
||||
sqliteFile,
|
||||
URI.create("https://api.example.com"),
|
||||
"gpt-4",
|
||||
30,
|
||||
3,
|
||||
100,
|
||||
-1,
|
||||
promptTemplateFile,
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
InvalidStartConfigurationException.class,
|
||||
() -> validator.validate(config)
|
||||
);
|
||||
assertTrue(exception.getMessage().contains("max.text.characters: must be > 0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_failsWhenSourceFolderDoesNotExist() throws Exception {
|
||||
Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
|
||||
Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
|
||||
Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));
|
||||
|
||||
StartConfiguration config = new StartConfiguration(
|
||||
tempDir.resolve("nonexistent"),
|
||||
targetFolder,
|
||||
sqliteFile,
|
||||
URI.create("https://api.example.com"),
|
||||
"gpt-4",
|
||||
30,
|
||||
3,
|
||||
100,
|
||||
50000,
|
||||
promptTemplateFile,
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
InvalidStartConfigurationException.class,
|
||||
() -> validator.validate(config)
|
||||
);
|
||||
assertTrue(exception.getMessage().contains("source.folder: path does not exist"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_failsWhenSourceFolderIsNotADirectory() throws Exception {
|
||||
Path sourceFile = Files.createFile(tempDir.resolve("sourcefile.txt"));
|
||||
Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
|
||||
Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
|
||||
Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));
|
||||
|
||||
StartConfiguration config = new StartConfiguration(
|
||||
sourceFile,
|
||||
targetFolder,
|
||||
sqliteFile,
|
||||
URI.create("https://api.example.com"),
|
||||
"gpt-4",
|
||||
30,
|
||||
3,
|
||||
100,
|
||||
50000,
|
||||
promptTemplateFile,
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
InvalidStartConfigurationException.class,
|
||||
() -> validator.validate(config)
|
||||
);
|
||||
assertTrue(exception.getMessage().contains("source.folder: path is not a directory"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_failsWhenTargetFolderDoesNotExist() throws Exception {
|
||||
Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
|
||||
Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
|
||||
Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));
|
||||
|
||||
StartConfiguration config = new StartConfiguration(
|
||||
sourceFolder,
|
||||
tempDir.resolve("nonexistent"),
|
||||
sqliteFile,
|
||||
URI.create("https://api.example.com"),
|
||||
"gpt-4",
|
||||
30,
|
||||
3,
|
||||
100,
|
||||
50000,
|
||||
promptTemplateFile,
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
InvalidStartConfigurationException.class,
|
||||
() -> validator.validate(config)
|
||||
);
|
||||
assertTrue(exception.getMessage().contains("target.folder: path does not exist"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_failsWhenTargetFolderIsNotADirectory() throws Exception {
|
||||
Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
|
||||
Path targetFile = Files.createFile(tempDir.resolve("targetfile.txt"));
|
||||
Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
|
||||
Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));
|
||||
|
||||
StartConfiguration config = new StartConfiguration(
|
||||
sourceFolder,
|
||||
targetFile,
|
||||
sqliteFile,
|
||||
URI.create("https://api.example.com"),
|
||||
"gpt-4",
|
||||
30,
|
||||
3,
|
||||
100,
|
||||
50000,
|
||||
promptTemplateFile,
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
InvalidStartConfigurationException.class,
|
||||
() -> validator.validate(config)
|
||||
);
|
||||
assertTrue(exception.getMessage().contains("target.folder: path is not a directory"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_failsWhenSqliteFileParentDoesNotExist() throws Exception {
|
||||
Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
|
||||
Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
|
||||
Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));
|
||||
|
||||
StartConfiguration config = new StartConfiguration(
|
||||
sourceFolder,
|
||||
targetFolder,
|
||||
tempDir.resolve("nonexistent/db.sqlite"),
|
||||
URI.create("https://api.example.com"),
|
||||
"gpt-4",
|
||||
30,
|
||||
3,
|
||||
100,
|
||||
50000,
|
||||
promptTemplateFile,
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
InvalidStartConfigurationException.class,
|
||||
() -> validator.validate(config)
|
||||
);
|
||||
assertTrue(exception.getMessage().contains("sqlite.file: parent directory does not exist"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_failsWhenApiBaseUrlIsNotAbsolute() throws Exception {
|
||||
Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
|
||||
Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
|
||||
Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
|
||||
Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));
|
||||
|
||||
StartConfiguration config = new StartConfiguration(
|
||||
sourceFolder,
|
||||
targetFolder,
|
||||
sqliteFile,
|
||||
URI.create("/api/v1"),
|
||||
"gpt-4",
|
||||
30,
|
||||
3,
|
||||
100,
|
||||
50000,
|
||||
promptTemplateFile,
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
InvalidStartConfigurationException.class,
|
||||
() -> validator.validate(config)
|
||||
);
|
||||
assertTrue(exception.getMessage().contains("api.baseUrl: must be an absolute URI"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_failsWhenApiBaseUrlHasUnsupportedScheme() throws Exception {
|
||||
Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
|
||||
Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
|
||||
Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
|
||||
Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));
|
||||
|
||||
StartConfiguration config = new StartConfiguration(
|
||||
sourceFolder,
|
||||
targetFolder,
|
||||
sqliteFile,
|
||||
URI.create("ftp://api.example.com"),
|
||||
"gpt-4",
|
||||
30,
|
||||
3,
|
||||
100,
|
||||
50000,
|
||||
promptTemplateFile,
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
InvalidStartConfigurationException.class,
|
||||
() -> validator.validate(config)
|
||||
);
|
||||
assertTrue(exception.getMessage().contains("api.baseUrl: scheme must be http or https"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_failsWhenPromptTemplateFileDoesNotExist() throws Exception {
|
||||
Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
|
||||
Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
|
||||
Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
|
||||
|
||||
StartConfiguration config = new StartConfiguration(
|
||||
sourceFolder,
|
||||
targetFolder,
|
||||
sqliteFile,
|
||||
URI.create("https://api.example.com"),
|
||||
"gpt-4",
|
||||
30,
|
||||
3,
|
||||
100,
|
||||
50000,
|
||||
tempDir.resolve("nonexistent.txt"),
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
InvalidStartConfigurationException.class,
|
||||
() -> validator.validate(config)
|
||||
);
|
||||
assertTrue(exception.getMessage().contains("prompt.template.file: path does not exist"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_failsWhenPromptTemplateFileIsNotARegularFile() throws Exception {
|
||||
Path sourceFolder = Files.createDirectory(tempDir.resolve("source"));
|
||||
Path targetFolder = Files.createDirectory(tempDir.resolve("target"));
|
||||
Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
|
||||
Path dirForPrompt = Files.createDirectory(tempDir.resolve("promptdir"));
|
||||
|
||||
StartConfiguration config = new StartConfiguration(
|
||||
sourceFolder,
|
||||
targetFolder,
|
||||
sqliteFile,
|
||||
URI.create("https://api.example.com"),
|
||||
"gpt-4",
|
||||
30,
|
||||
3,
|
||||
100,
|
||||
50000,
|
||||
dirForPrompt,
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
InvalidStartConfigurationException.class,
|
||||
() -> validator.validate(config)
|
||||
);
|
||||
assertTrue(exception.getMessage().contains("prompt.template.file: path is not a regular file"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_failsWhenSourceAndTargetAreSamePath() throws Exception {
|
||||
Path sameFolder = Files.createDirectory(tempDir.resolve("samefolder"));
|
||||
Path sqliteFile = Files.createFile(tempDir.resolve("db.sqlite"));
|
||||
Path promptTemplateFile = Files.createFile(tempDir.resolve("prompt.txt"));
|
||||
|
||||
StartConfiguration config = new StartConfiguration(
|
||||
sameFolder,
|
||||
sameFolder,
|
||||
sqliteFile,
|
||||
URI.create("https://api.example.com"),
|
||||
"gpt-4",
|
||||
30,
|
||||
3,
|
||||
100,
|
||||
50000,
|
||||
promptTemplateFile,
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
InvalidStartConfigurationException.class,
|
||||
() -> validator.validate(config)
|
||||
);
|
||||
assertTrue(exception.getMessage().contains("source.folder and target.folder must not resolve to the same path"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_failsWhenMultipleErrorsOccur() {
|
||||
StartConfiguration config = new StartConfiguration(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
0,
|
||||
-1,
|
||||
0,
|
||||
-1,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"INFO",
|
||||
"test-api-key"
|
||||
);
|
||||
|
||||
InvalidStartConfigurationException exception = assertThrows(
|
||||
InvalidStartConfigurationException.class,
|
||||
() -> validator.validate(config)
|
||||
);
|
||||
String message = exception.getMessage();
|
||||
assertTrue(message.contains("source.folder: must not be null"));
|
||||
assertTrue(message.contains("target.folder: must not be null"));
|
||||
assertTrue(message.contains("sqlite.file: must not be null"));
|
||||
assertTrue(message.contains("api.baseUrl: must not be null"));
|
||||
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.pages: must be > 0"));
|
||||
assertTrue(message.contains("max.text.characters: must be > 0"));
|
||||
}
|
||||
}
|
||||
1
pdf-umbenenner-application/src/test/resources/.gitkeep
Normal file
1
pdf-umbenenner-application/src/test/resources/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
# Keep directory
|
||||
Reference in New Issue
Block a user