Diagnoselogs und Test für DB-Reset-Verifikation (FAILED_FINAL)

- [TEMP-TRACE] INFO-Logs in SqliteDocumentRecordRepositoryAdapter:
  deleteByFingerprint() zeigt Fingerprint, jdbcUrl und rowsAffected;
  findByFingerprint() zeigt Fingerprint, jdbcUrl und Lookup-Ergebnis
- [TEMP-TRACE] Log in DocumentProcessingCoordinator.processDeferredOutcome()
  zeigt Fingerprint und Lookup-Ergebnis-Typ nach DB-Abfrage
- Bestehende [TEMP-TRACE] Logs in GuiBatchRunCoordinator und
  SqliteUnitOfWorkAdapter sind ebenfalls enthalten
- Neuer Test resetDocumentByFingerprint_deletesFailedFinalRecord_resultIsDocumentUnknown:
  legt FAILED_FINAL-Datensatz in echter SQLite-DB an, führt Reset aus
  und prüft, dass der Datensatz danach DocumentUnknown zurückliefert

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-04-23 15:00:22 +02:00
parent 83f6d63c27
commit 55088354ab
5 changed files with 62 additions and 4 deletions
@@ -298,7 +298,13 @@ public final class GuiBatchRunCoordinator {
// Reset the database status synchronously before starting the mini-run. // Reset the database status synchronously before starting the mini-run.
// This ensures that documents are not skipped due to FAILED_FINAL or other // This ensures that documents are not skipped due to FAILED_FINAL or other
// terminal states. // terminal states.
LOG.info("[TEMP-TRACE] startReprocessing: Konfiguration={}, Fingerprints={}",
configFilePath, fingerprintFilter.stream()
.map(f -> f.sha256Hex().substring(0, 8) + "")
.collect(java.util.stream.Collectors.joining(", ")));
ResetDocumentStatusResult resetResult = resetPort.reset(configFilePath, fingerprintFilter); ResetDocumentStatusResult resetResult = resetPort.reset(configFilePath, fingerprintFilter);
LOG.info("[TEMP-TRACE] startReprocessing: Reset-Ergebnis: {} erfolgreich, {} fehlgeschlagen; Fehler={}",
resetResult.successCount(), resetResult.failureCount(), resetResult.failures());
if (resetResult.successCount() == 0) { if (resetResult.successCount() == 0) {
LOG.warn("GUI-Reprocessing: Reset für alle {} Dokumente fehlgeschlagen; " LOG.warn("GUI-Reprocessing: Reset für alle {} Dokumente fehlgeschlagen; "
+ "Mini-Lauf wird nicht gestartet.", fingerprintFilter.size()); + "Mini-Lauf wird nicht gestartet.", fingerprintFilter.size());
@@ -101,16 +101,20 @@ public class SqliteDocumentRecordRepositoryAdapter implements DocumentRecordRepo
if (rs.next()) { if (rs.next()) {
// Document exists - map to appropriate result type based on status // Document exists - map to appropriate result type based on status
DocumentRecord record = mapResultSetToDocumentRecord(rs, fingerprint); DocumentRecord record = mapResultSetToDocumentRecord(rs, fingerprint);
DocumentRecordLookupResult result = switch (record.overallStatus()) {
return switch (record.overallStatus()) {
case SUCCESS -> new DocumentTerminalSuccess(record); case SUCCESS -> new DocumentTerminalSuccess(record);
case FAILED_FINAL -> new DocumentTerminalFinalFailure(record); case FAILED_FINAL -> new DocumentTerminalFinalFailure(record);
case READY_FOR_AI, PROPOSAL_READY, PROCESSING, FAILED_RETRYABLE, case READY_FOR_AI, PROPOSAL_READY, PROCESSING, FAILED_RETRYABLE,
SKIPPED_ALREADY_PROCESSED, SKIPPED_FINAL_FAILURE -> SKIPPED_ALREADY_PROCESSED, SKIPPED_FINAL_FAILURE ->
new DocumentKnownProcessable(record); new DocumentKnownProcessable(record);
}; };
logger.info("[TEMP-TRACE] findByFingerprint: fingerprint={}, jdbcUrl={}, Ergebnis={}",
fingerprint.sha256Hex(), jdbcUrl, result.getClass().getSimpleName());
return result;
} else { } else {
// Document not found // Document not found
logger.info("[TEMP-TRACE] findByFingerprint: fingerprint={}, jdbcUrl={}, Ergebnis=DocumentUnknown",
fingerprint.sha256Hex(), jdbcUrl);
return new DocumentUnknown(); return new DocumentUnknown();
} }
} }
@@ -318,8 +322,8 @@ public class SqliteDocumentRecordRepositoryAdapter implements DocumentRecordRepo
statement.setString(1, fingerprint.sha256Hex()); statement.setString(1, fingerprint.sha256Hex());
int rowsAffected = statement.executeUpdate(); int rowsAffected = statement.executeUpdate();
logger.debug("Deleted {} document_record row(s) for fingerprint: {}", logger.info("[TEMP-TRACE] deleteByFingerprint: fingerprint={}, jdbcUrl={}, rowsAffected={}",
rowsAffected, fingerprint.sha256Hex()); fingerprint.sha256Hex(), jdbcUrl, rowsAffected);
} catch (SQLException e) { } catch (SQLException e) {
String message = "Failed to delete document record for fingerprint '" String message = "Failed to delete document record for fingerprint '"
@@ -178,6 +178,8 @@ public class SqliteUnitOfWorkAdapter implements UnitOfWorkPort {
*/ */
@Override @Override
public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) { public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) {
logger.info("[TEMP-TRACE] resetDocumentByFingerprint: Fingerprint={}, jdbcUrl={}",
fingerprint.sha256Hex(), jdbcUrl);
// Delete attempts first (FK constraint: processing_attempt → document_record) // Delete attempts first (FK constraint: processing_attempt → document_record)
SqliteProcessingAttemptRepositoryAdapter attemptRepo = SqliteProcessingAttemptRepositoryAdapter attemptRepo =
new SqliteProcessingAttemptRepositoryAdapter(jdbcUrl) { new SqliteProcessingAttemptRepositoryAdapter(jdbcUrl) {
@@ -197,6 +199,8 @@ public class SqliteUnitOfWorkAdapter implements UnitOfWorkPort {
} }
}; };
recordRepo.deleteByFingerprint(fingerprint); recordRepo.deleteByFingerprint(fingerprint);
logger.info("[TEMP-TRACE] resetDocumentByFingerprint: Löschung abgeschlossen für Fingerprint={}",
fingerprint.sha256Hex());
} }
} }
} }
@@ -16,6 +16,7 @@ import org.junit.jupiter.api.io.TempDir;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException; 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.DocumentRecord;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentTerminalFinalFailure;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentUnknown; import de.gecheckt.pdf.umbenenner.application.port.out.DocumentUnknown;
import de.gecheckt.pdf.umbenenner.application.port.out.FailureCounters; import de.gecheckt.pdf.umbenenner.application.port.out.FailureCounters;
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttempt; import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttempt;
@@ -295,4 +296,45 @@ class SqliteUnitOfWorkAdapterTest {
assertThat(docRepository.findByFingerprint(fingerprint)).isInstanceOf(DocumentUnknown.class); assertThat(docRepository.findByFingerprint(fingerprint)).isInstanceOf(DocumentUnknown.class);
} }
/**
* Stellt sicher, dass ein Dokument mit Status FAILED_FINAL nach dem Reset nicht mehr
* auffindbar ist. Dieser Test deckt den Reprocessing-Pfad ab, bei dem der GUI-Nutzer
* ein final fehlgeschlagenes Dokument erneut verarbeiten lässt.
*/
@Test
void resetDocumentByFingerprint_deletesFailedFinalRecord_resultIsDocumentUnknown() {
DocumentFingerprint fingerprint = new DocumentFingerprint(
"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd");
Instant now = Instant.now().truncatedTo(ChronoUnit.MICROS);
DocumentRecord record = new DocumentRecord(
fingerprint,
new SourceDocumentLocator("/source/failed-final.pdf"),
"failed-final.pdf",
ProcessingStatus.FAILED_FINAL,
new FailureCounters(2, 0),
now.minusSeconds(10),
null,
now.minusSeconds(20),
now,
null,
null
);
SqliteDocumentRecordRepositoryAdapter docRepository =
new SqliteDocumentRecordRepositoryAdapter(jdbcUrl);
// Datensatz mit FAILED_FINAL anlegen
unitOfWorkAdapter.executeInTransaction(txOps -> txOps.createDocumentRecord(record));
assertThat(docRepository.findByFingerprint(fingerprint))
.isInstanceOf(DocumentTerminalFinalFailure.class);
// Reset ausführen
unitOfWorkAdapter.executeInTransaction(txOps ->
txOps.resetDocumentByFingerprint(fingerprint));
// Nach dem Reset muss der Datensatz vollständig entfernt sein
assertThat(docRepository.findByFingerprint(fingerprint))
.isInstanceOf(DocumentUnknown.class);
}
} }
@@ -351,6 +351,8 @@ public class DocumentProcessingCoordinator {
// Step 1: Load the document master record // Step 1: Load the document master record
DocumentRecordLookupResult lookupResult = DocumentRecordLookupResult lookupResult =
documentRecordRepository.findByFingerprint(fingerprint); documentRecordRepository.findByFingerprint(fingerprint);
logger.info("[TEMP-TRACE] processDeferredOutcome: Fingerprint={}, Lookup-Ergebnis={}",
fingerprint.sha256Hex(), lookupResult.getClass().getSimpleName());
// Step 2: Handle persistence lookup failure // Step 2: Handle persistence lookup failure
if (lookupResult instanceof PersistenceLookupTechnicalFailure failure) { if (lookupResult instanceof PersistenceLookupTechnicalFailure failure) {