1
0

M4 AP-008 Testabdeckung für Fingerprint, Persistenz und Skip-Logik

vervollständigen
This commit is contained in:
2026-04-03 14:18:31 +02:00
parent a35ac5c8f1
commit 326e739e45
3 changed files with 197 additions and 25 deletions

1
.claude/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/settings.local.json

View File

@@ -1,25 +0,0 @@
{
"permissions": {
"allow": [
"Bash(xargs grep:*)",
"Bash(xargs wc:*)",
"Bash(mvn clean:*)",
"Bash(mvn verify:*)",
"Bash(mvn test:*)",
"Bash(find D:/Dev/Projects/pdf-umbenenner-parent -not -path */target/* -type d)",
"Bash(mvn -pl pdf-umbenenner-adapter-out clean compile)",
"Bash(mvn dependency:tree -pl pdf-umbenenner-adapter-out)",
"Bash(mvn -pl pdf-umbenenner-domain clean compile)",
"Bash(mvn help:describe -Dplugin=org.apache.pdfbox:pdfbox -Ddetail=false)",
"Bash(cd /d D:/Dev/Projects/pdf-umbenenner-parent)",
"Bash(mvn -v)",
"Bash(grep -E \"\\\\.java$\")",
"Bash(grep \"\\\\.java$\")",
"Bash(mvn -q clean compile -DskipTests)",
"Bash(mvn -q test)",
"Bash(mvn -q clean test)",
"Bash(git add:*)",
"Bash(git commit -m ':*)"
]
}
}

View File

@@ -15,6 +15,7 @@ import de.gecheckt.pdf.umbenenner.application.port.out.DocumentKnownProcessable;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecordLookupResult;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentTerminalFinalFailure;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentTerminalSuccess;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentUnknown;
import de.gecheckt.pdf.umbenenner.application.port.out.FailureCounters;
@@ -203,4 +204,199 @@ class SqliteDocumentRecordRepositoryAdapterTest {
.isInstanceOf(DocumentPersistenceException.class)
.hasMessageContaining("Expected to update 1 row but affected 0 rows");
}
@Test
void findByFingerprint_shouldReturnDocumentTerminalFinalFailure_whenStatusIsFailedFinal() {
// Given
DocumentFingerprint fingerprint = new DocumentFingerprint(
"5555555555555555555555555555555555555555555555555555555555555555");
Instant now = Instant.now().truncatedTo(ChronoUnit.MICROS);
// Create initially as PROCESSING
DocumentRecord initialRecord = new DocumentRecord(
fingerprint,
new SourceDocumentLocator("/path/to/document.pdf"),
"document.pdf",
ProcessingStatus.PROCESSING,
FailureCounters.zero(),
null,
null,
now.minusSeconds(120),
now.minusSeconds(120)
);
repository.create(initialRecord);
// Update to FAILED_FINAL (second content error: count=2)
Instant failureInstant = now.minusSeconds(60);
DocumentRecord failedFinalRecord = new DocumentRecord(
fingerprint,
new SourceDocumentLocator("/path/to/document.pdf"),
"document.pdf",
ProcessingStatus.FAILED_FINAL,
new FailureCounters(2, 0),
failureInstant,
null,
now.minusSeconds(120),
failureInstant
);
repository.update(failedFinalRecord);
// When
DocumentRecordLookupResult result = repository.findByFingerprint(fingerprint);
// Then
assertThat(result).isInstanceOf(DocumentTerminalFinalFailure.class);
DocumentTerminalFinalFailure terminalFailure = (DocumentTerminalFinalFailure) result;
DocumentRecord foundRecord = terminalFailure.record();
assertThat(foundRecord.overallStatus()).isEqualTo(ProcessingStatus.FAILED_FINAL);
assertThat(foundRecord.failureCounters().contentErrorCount()).isEqualTo(2);
assertThat(foundRecord.failureCounters().transientErrorCount()).isEqualTo(0);
assertThat(foundRecord.lastFailureInstant()).isEqualTo(failureInstant);
assertThat(foundRecord.lastSuccessInstant()).isNull();
}
@Test
void update_shouldPersistNonZeroFailureCountersAndFailureInstant() {
// Given: create a new document as PROCESSING
DocumentFingerprint fingerprint = new DocumentFingerprint(
"6666666666666666666666666666666666666666666666666666666666666666");
Instant createdAt = Instant.now().minusSeconds(120).truncatedTo(ChronoUnit.MICROS);
DocumentRecord initialRecord = new DocumentRecord(
fingerprint,
new SourceDocumentLocator("/source/invoice.pdf"),
"invoice.pdf",
ProcessingStatus.PROCESSING,
FailureCounters.zero(),
null,
null,
createdAt,
createdAt
);
repository.create(initialRecord);
// Update to FAILED_RETRYABLE with content_error_count=1, transient_error_count=0
Instant failureInstant = Instant.now().truncatedTo(ChronoUnit.MICROS);
DocumentRecord failedRetryableRecord = new DocumentRecord(
fingerprint,
new SourceDocumentLocator("/source/invoice.pdf"),
"invoice.pdf",
ProcessingStatus.FAILED_RETRYABLE,
new FailureCounters(1, 0),
failureInstant,
null,
createdAt,
failureInstant
);
// When
repository.update(failedRetryableRecord);
DocumentRecordLookupResult result = repository.findByFingerprint(fingerprint);
// Then: lookup returns DocumentKnownProcessable (FAILED_RETRYABLE is not terminal)
assertThat(result).isInstanceOf(DocumentKnownProcessable.class);
DocumentKnownProcessable known = (DocumentKnownProcessable) result;
DocumentRecord foundRecord = known.record();
assertThat(foundRecord.overallStatus()).isEqualTo(ProcessingStatus.FAILED_RETRYABLE);
assertThat(foundRecord.failureCounters().contentErrorCount()).isEqualTo(1);
assertThat(foundRecord.failureCounters().transientErrorCount()).isEqualTo(0);
assertThat(foundRecord.lastFailureInstant()).isEqualTo(failureInstant);
assertThat(foundRecord.lastSuccessInstant()).isNull();
assertThat(foundRecord.createdAt()).isEqualTo(createdAt);
assertThat(foundRecord.updatedAt()).isEqualTo(failureInstant);
}
@Test
void update_shouldTransitionFromFailedRetryableToFailedFinalWithIncrementedCounter() {
// Given: create as FAILED_RETRYABLE with content_error_count=1 (first content error already recorded)
DocumentFingerprint fingerprint = new DocumentFingerprint(
"7777777777777777777777777777777777777777777777777777777777777777");
Instant createdAt = Instant.now().minusSeconds(300).truncatedTo(ChronoUnit.MICROS);
Instant firstFailureAt = Instant.now().minusSeconds(120).truncatedTo(ChronoUnit.MICROS);
DocumentRecord initialRecord = new DocumentRecord(
fingerprint,
new SourceDocumentLocator("/source/report.pdf"),
"report.pdf",
ProcessingStatus.FAILED_RETRYABLE,
new FailureCounters(1, 0),
firstFailureAt,
null,
createdAt,
firstFailureAt
);
repository.create(initialRecord);
// Second content error: update to FAILED_FINAL with content_error_count=2
Instant secondFailureAt = Instant.now().truncatedTo(ChronoUnit.MICROS);
DocumentRecord failedFinalRecord = new DocumentRecord(
fingerprint,
new SourceDocumentLocator("/source/report.pdf"),
"report.pdf",
ProcessingStatus.FAILED_FINAL,
new FailureCounters(2, 0),
secondFailureAt,
null,
createdAt,
secondFailureAt
);
// When
repository.update(failedFinalRecord);
DocumentRecordLookupResult result = repository.findByFingerprint(fingerprint);
// Then: terminal final failure
assertThat(result).isInstanceOf(DocumentTerminalFinalFailure.class);
DocumentTerminalFinalFailure terminalFailure = (DocumentTerminalFinalFailure) result;
DocumentRecord foundRecord = terminalFailure.record();
assertThat(foundRecord.overallStatus()).isEqualTo(ProcessingStatus.FAILED_FINAL);
assertThat(foundRecord.failureCounters().contentErrorCount()).isEqualTo(2);
assertThat(foundRecord.failureCounters().transientErrorCount()).isEqualTo(0);
assertThat(foundRecord.lastFailureInstant()).isEqualTo(secondFailureAt);
assertThat(foundRecord.lastSuccessInstant()).isNull();
}
@Test
void update_shouldPersistTransientErrorCounter() {
// Given: create as PROCESSING
DocumentFingerprint fingerprint = new DocumentFingerprint(
"8888888888888888888888888888888888888888888888888888888888888888");
Instant createdAt = Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MICROS);
DocumentRecord initialRecord = new DocumentRecord(
fingerprint,
new SourceDocumentLocator("/source/scan.pdf"),
"scan.pdf",
ProcessingStatus.PROCESSING,
FailureCounters.zero(),
null,
null,
createdAt,
createdAt
);
repository.create(initialRecord);
// Update to FAILED_RETRYABLE with transient_error_count=3 (technical errors)
Instant failureInstant = Instant.now().truncatedTo(ChronoUnit.MICROS);
DocumentRecord transientFailureRecord = new DocumentRecord(
fingerprint,
new SourceDocumentLocator("/source/scan.pdf"),
"scan.pdf",
ProcessingStatus.FAILED_RETRYABLE,
new FailureCounters(0, 3),
failureInstant,
null,
createdAt,
failureInstant
);
// When
repository.update(transientFailureRecord);
DocumentRecordLookupResult result = repository.findByFingerprint(fingerprint);
// Then
assertThat(result).isInstanceOf(DocumentKnownProcessable.class);
DocumentKnownProcessable known = (DocumentKnownProcessable) result;
DocumentRecord foundRecord = known.record();
assertThat(foundRecord.failureCounters().contentErrorCount()).isEqualTo(0);
assertThat(foundRecord.failureCounters().transientErrorCount()).isEqualTo(3);
assertThat(foundRecord.lastFailureInstant()).isEqualTo(failureInstant);
}
}