1
0

M4 Nachbearbeitung Bootstrap Tests verifiziert und ergänzt

This commit is contained in:
2026-04-06 21:01:49 +02:00
parent 2d7be60057
commit ac02057991
3 changed files with 209 additions and 0 deletions

View File

@@ -200,6 +200,50 @@ class PdfTextExtractionPortAdapterTest {
assertFalse(error.errorMessage().isBlank(), "TechnicalError message must not be blank"); assertFalse(error.errorMessage().isBlank(), "TechnicalError message must not be blank");
} }
@Test
void testExtractingLargePdfReturnsSuccess() throws Exception {
// Create a large PDF with many pages
Path largePdfFile = tempDir.resolve("large.pdf");
createMultiPagePdf(largePdfFile, 50);
SourceDocumentCandidate candidate = new SourceDocumentCandidate(
"large.pdf",
Files.size(largePdfFile),
new SourceDocumentLocator(largePdfFile.toAbsolutePath().toString())
);
PdfExtractionResult result = adapter.extractTextAndPageCount(candidate);
assertInstanceOf(PdfExtractionSuccess.class, result);
PdfExtractionSuccess success = (PdfExtractionSuccess) result;
assertEquals(50, success.pageCount().value());
assertNotNull(success.extractedText());
}
@Test
void testPartiallyCorruptedPdfStillReturnsError() throws Exception {
// Create a file that starts like PDF but has corrupted content
Path partialCorruptFile = tempDir.resolve("partial-corrupt.pdf");
byte[] pdfSignature = new byte[] {0x25, 0x50, 0x44, 0x46}; // %PDF in hex
byte[] corruptData = "This is corrupted PDF content that will fail parsing".getBytes();
byte[] combined = new byte[pdfSignature.length + corruptData.length];
System.arraycopy(pdfSignature, 0, combined, 0, pdfSignature.length);
System.arraycopy(corruptData, 0, combined, pdfSignature.length, corruptData.length);
Files.write(partialCorruptFile, combined);
SourceDocumentCandidate candidate = new SourceDocumentCandidate(
"partial-corrupt.pdf",
Files.size(partialCorruptFile),
new SourceDocumentLocator(partialCorruptFile.toAbsolutePath().toString())
);
PdfExtractionResult result = adapter.extractTextAndPageCount(candidate);
assertInstanceOf(PdfExtractionTechnicalError.class, result);
PdfExtractionTechnicalError error = (PdfExtractionTechnicalError) result;
assertNotNull(error.errorMessage());
}
// --- Helper methods to create test PDFs --- // --- Helper methods to create test PDFs ---
/** /**

View File

@@ -399,4 +399,158 @@ class SqliteDocumentRecordRepositoryAdapterTest {
assertThat(foundRecord.failureCounters().transientErrorCount()).isEqualTo(3); assertThat(foundRecord.failureCounters().transientErrorCount()).isEqualTo(3);
assertThat(foundRecord.lastFailureInstant()).isEqualTo(failureInstant); assertThat(foundRecord.lastFailureInstant()).isEqualTo(failureInstant);
} }
@Test
void findByFingerprint_shouldThrowNullPointerException_whenFingerprintIsNull() {
// Given
DocumentFingerprint nullFingerprint = null;
// When / Then
assertThatThrownBy(() -> repository.findByFingerprint(nullFingerprint))
.isInstanceOf(NullPointerException.class);
}
@Test
void create_shouldThrowNullPointerException_whenRecordIsNull() {
// When / Then
assertThatThrownBy(() -> repository.create(null))
.isInstanceOf(NullPointerException.class);
}
@Test
void update_shouldThrowNullPointerException_whenRecordIsNull() {
// When / Then
assertThatThrownBy(() -> repository.update(null))
.isInstanceOf(NullPointerException.class);
}
@Test
void findByFingerprint_shouldReturnDocumentKnownProcessable_whenStatusIsSkippedAlreadyProcessed() {
// Given
DocumentFingerprint fingerprint = new DocumentFingerprint(
"9999999999999999999999999999999999999999999999999999999999999999");
Instant now = Instant.now().truncatedTo(ChronoUnit.MICROS);
DocumentRecord record = new DocumentRecord(
fingerprint,
new SourceDocumentLocator("/source/skipped.pdf"),
"skipped.pdf",
ProcessingStatus.SKIPPED_ALREADY_PROCESSED,
FailureCounters.zero(),
null,
null,
now,
now
);
repository.create(record);
// When
DocumentRecordLookupResult result = repository.findByFingerprint(fingerprint);
// Then: SKIPPED_ALREADY_PROCESSED is not terminal, should be DocumentKnownProcessable
assertThat(result).isInstanceOf(DocumentKnownProcessable.class);
DocumentKnownProcessable known = (DocumentKnownProcessable) result;
assertThat(known.record().overallStatus()).isEqualTo(ProcessingStatus.SKIPPED_ALREADY_PROCESSED);
}
@Test
void findByFingerprint_shouldReturnDocumentKnownProcessable_whenStatusIsSkippedFinalFailure() {
// Given
DocumentFingerprint fingerprint = new DocumentFingerprint(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
Instant now = Instant.now().truncatedTo(ChronoUnit.MICROS);
DocumentRecord record = new DocumentRecord(
fingerprint,
new SourceDocumentLocator("/source/final-skipped.pdf"),
"final-skipped.pdf",
ProcessingStatus.SKIPPED_FINAL_FAILURE,
new FailureCounters(2, 0),
now.minusSeconds(60),
null,
now,
now
);
repository.create(record);
// When
DocumentRecordLookupResult result = repository.findByFingerprint(fingerprint);
// Then: SKIPPED_FINAL_FAILURE is not terminal, should be DocumentKnownProcessable
assertThat(result).isInstanceOf(DocumentKnownProcessable.class);
DocumentKnownProcessable known = (DocumentKnownProcessable) result;
assertThat(known.record().overallStatus()).isEqualTo(ProcessingStatus.SKIPPED_FINAL_FAILURE);
}
@Test
void create_and_update_shouldPreserveNullTimestamps() {
// Given: create with null timestamps
DocumentFingerprint fingerprint = new DocumentFingerprint(
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
Instant now = Instant.now().truncatedTo(ChronoUnit.MICROS);
DocumentRecord record = new DocumentRecord(
fingerprint,
new SourceDocumentLocator("/source/no-timestamps.pdf"),
"no-timestamps.pdf",
ProcessingStatus.PROCESSING,
FailureCounters.zero(),
null, // lastFailureInstant is null
null, // lastSuccessInstant is null
now,
now
);
repository.create(record);
// When
DocumentRecordLookupResult result = repository.findByFingerprint(fingerprint);
// Then
assertThat(result).isInstanceOf(DocumentKnownProcessable.class);
DocumentKnownProcessable known = (DocumentKnownProcessable) result;
assertThat(known.record().lastFailureInstant()).isNull();
assertThat(known.record().lastSuccessInstant()).isNull();
}
@Test
void update_shouldPreserveCreatedAtTimestamp() {
// Given: create with specific createdAt
DocumentFingerprint fingerprint = new DocumentFingerprint(
"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc");
Instant createdAt = Instant.now().minusSeconds(1000).truncatedTo(ChronoUnit.MICROS);
Instant now = Instant.now().truncatedTo(ChronoUnit.MICROS);
DocumentRecord initialRecord = new DocumentRecord(
fingerprint,
new SourceDocumentLocator("/source/test.pdf"),
"test.pdf",
ProcessingStatus.PROCESSING,
FailureCounters.zero(),
null,
null,
createdAt, // Much older createdAt
createdAt
);
repository.create(initialRecord);
// Update with new timestamps
DocumentRecord updated = new DocumentRecord(
fingerprint,
new SourceDocumentLocator("/source/test.pdf"),
"test.pdf",
ProcessingStatus.SUCCESS,
FailureCounters.zero(),
null,
now,
createdAt, // createdAt should remain unchanged
now
);
// When
repository.update(updated);
DocumentRecordLookupResult result = repository.findByFingerprint(fingerprint);
// Then: createdAt should be preserved
assertThat(result).isInstanceOf(DocumentTerminalSuccess.class);
DocumentTerminalSuccess success = (DocumentTerminalSuccess) result;
assertThat(success.record().createdAt()).isEqualTo(createdAt);
assertThat(success.record().updatedAt()).isEqualTo(now);
}
} }

View File

@@ -179,4 +179,15 @@ class SqliteUnitOfWorkAdapterTest {
assertTrue(lookupResult instanceof de.gecheckt.pdf.umbenenner.application.port.out.DocumentUnknown, assertTrue(lookupResult instanceof de.gecheckt.pdf.umbenenner.application.port.out.DocumentUnknown,
"DocumentRecord should be rolled back on runtime exception"); "DocumentRecord should be rolled back on runtime exception");
} }
/**
* Verifies that null operations Consumer throws NullPointerException.
*/
@Test
void executeInTransaction_throwsNullPointerExceptionForNullOperations() {
assertThrows(NullPointerException.class, () -> {
unitOfWorkAdapter.executeInTransaction(null);
});
}
} }