1
0

Implementierung für M2 vorläufig abgeschlossen

This commit is contained in:
2026-03-31 21:52:48 +02:00
parent 301f1acf08
commit 9d66a446b3
29 changed files with 1892 additions and 52 deletions

View File

@@ -0,0 +1,188 @@
package de.gecheckt.pdf.umbenenner.application.usecase;
import de.gecheckt.pdf.umbenenner.application.config.StartConfiguration;
import de.gecheckt.pdf.umbenenner.application.port.in.BatchRunOutcome;
import de.gecheckt.pdf.umbenenner.application.port.out.ConfigurationPort;
import de.gecheckt.pdf.umbenenner.application.port.out.RunLockPort;
import de.gecheckt.pdf.umbenenner.application.port.out.RunLockUnavailableException;
import de.gecheckt.pdf.umbenenner.domain.model.BatchRunContext;
import de.gecheckt.pdf.umbenenner.domain.model.RunId;
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 java.time.Instant;
import static org.junit.jupiter.api.Assertions.*;
/**
* Tests for {@link M2BatchRunProcessingUseCase}.
* <p>
* Verifies correct orchestration of the M2 batch cycle including lock management,
* configuration loading, and controlled execution flow.
*/
class M2BatchRunProcessingUseCaseTest {
@TempDir
Path tempDir;
@Test
void execute_successfullyAcquiresAndReleasesLock() {
// Setup mock ports that track invocations
MockRunLockPort lockPort = new MockRunLockPort();
MockConfigurationPort configPort = new MockConfigurationPort(tempDir);
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(configPort, lockPort);
BatchRunContext context = new BatchRunContext(new RunId("test-run-1"), Instant.now());
// Execute
BatchRunOutcome outcome = useCase.execute(context);
// Verify lock lifecycle
assertTrue(lockPort.wasAcquireCalled(), "Lock acquire should be called");
assertTrue(lockPort.wasReleaseCalled(), "Lock release should be called");
assertTrue(outcome.isSuccess(), "Batch should complete successfully");
}
@Test
void execute_returnsLockUnavailableWhenLockCannotBeAcquired() {
// Setup: lock port that fails to acquire (another instance is running)
RunLockPort lockPort = new RunLockPort() {
@Override
public void acquire() {
throw new RunLockUnavailableException("Another instance already running");
}
@Override
public void release() {
// Nothing to release
}
};
ConfigurationPort configPort = new MockConfigurationPort(tempDir);
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(configPort, lockPort);
BatchRunContext context = new BatchRunContext(new RunId("test-run-2"), Instant.now());
// Execute
BatchRunOutcome outcome = useCase.execute(context);
// AP-007: lock unavailable is a distinct, controlled early-termination outcome
assertTrue(outcome.isLockUnavailable(), "Outcome should be LOCK_UNAVAILABLE when lock cannot be acquired");
assertTrue(outcome.isFailure(), "LOCK_UNAVAILABLE also reports as failure for exit code derivation");
assertFalse(outcome.isSuccess(), "Batch should not succeed when lock unavailable");
}
@Test
void execute_releasesLockEvenOnError() {
// Setup: mock lock port that tracks release calls
MockRunLockPort lockPort = new MockRunLockPort();
// Config port that throws exception
ConfigurationPort configPort = () -> {
throw new RuntimeException("Configuration loading error");
};
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(configPort, lockPort);
BatchRunContext context = new BatchRunContext(new RunId("test-run-3"), Instant.now());
// Execute (expect failure due to config error)
BatchRunOutcome outcome = useCase.execute(context);
// Verify lock is still released despite error
assertTrue(lockPort.wasReleaseCalled(), "Lock should be released even on configuration error");
assertTrue(outcome.isFailure(), "Batch should fail");
}
@Test
void execute_loadsConfigurationDuringExecution() {
// Setup
MockRunLockPort lockPort = new MockRunLockPort();
MockConfigurationPort configPort = new MockConfigurationPort(tempDir);
M2BatchRunProcessingUseCase useCase = new M2BatchRunProcessingUseCase(configPort, lockPort);
BatchRunContext context = new BatchRunContext(new RunId("test-run-4"), Instant.now());
// Execute
BatchRunOutcome outcome = useCase.execute(context);
// Verify configuration was loaded
assertTrue(configPort.wasLoadConfigurationCalled(), "Configuration should be loaded");
assertTrue(outcome.isSuccess(), "Batch should succeed");
}
/**
* Mock ConfigurationPort for testing.
*/
private static class MockConfigurationPort implements ConfigurationPort {
private final Path tempDir;
private boolean loadConfigurationCalled = false;
MockConfigurationPort(Path tempDir) {
this.tempDir = tempDir;
}
@Override
public StartConfiguration loadConfiguration() {
loadConfigurationCalled = true;
try {
Path sourceDir = Files.createDirectories(tempDir.resolve("source"));
Path targetDir = Files.createDirectories(tempDir.resolve("target"));
Path dbFile = Files.createFile(tempDir.resolve("db.sqlite"));
Path promptFile = Files.createFile(tempDir.resolve("prompt.txt"));
return new StartConfiguration(
sourceDir,
targetDir,
dbFile,
URI.create("https://api.example.com"),
"gpt-4",
30,
3,
100,
50000,
promptFile,
tempDir.resolve("lock.lock"),
tempDir.resolve("logs"),
"INFO",
"test-key"
);
} catch (Exception e) {
throw new RuntimeException("Failed to create mock configuration", e);
}
}
boolean wasLoadConfigurationCalled() {
return loadConfigurationCalled;
}
}
/**
* Mock RunLockPort for testing.
*/
private static class MockRunLockPort implements RunLockPort {
private boolean acquireCalled = false;
private boolean releaseCalled = false;
@Override
public void acquire() {
acquireCalled = true;
}
@Override
public void release() {
releaseCalled = true;
}
boolean wasAcquireCalled() {
return acquireCalled;
}
boolean wasReleaseCalled() {
return releaseCalled;
}
}
}