M4 AP-008 Testabdeckung für Fingerprint, Persistenz und Skip-Logik
vervollständigen
This commit is contained in:
1
.claude/.gitignore
vendored
Normal file
1
.claude/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/settings.local.json
|
||||
@@ -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 ':*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user