M4 AP-008 Testabdeckung für Fingerprint, Persistenz und Skip-Logik
vervollständigen
This commit is contained in:
@@ -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