M4 Nachbearbeitung Bootstrap Tests verifiziert und ergänzt
This commit is contained in:
@@ -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 ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user