#7: Historien-Tab mit Liste, Detail, Filter, Status-Reset und Eintrag-Loeschen

Implementiert den vollstaendigen Historien-Tab (Verlauf) als vierten Tab der GUI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-30 13:57:07 +02:00
parent 5d5dee0bbf
commit 46fc1d4fa4
31 changed files with 3095 additions and 17 deletions
@@ -60,5 +60,28 @@ public interface UnitOfWorkPort {
* @throws DocumentPersistenceException if the delete fails due to a technical error
*/
void resetDocumentByFingerprint(DocumentFingerprint fingerprint);
/**
* Setzt ausschließlich die vier fachlich relevanten Status-Felder zurück,
* ohne die Versuchshistorie zu löschen.
* <p>
* Folgende Felder werden aktualisiert:
* <ul>
* <li>{@code overall_status} → {@code READY_FOR_AI}</li>
* <li>{@code content_error_count} → {@code 0}</li>
* <li>{@code transient_error_count} → {@code 0}</li>
* <li>{@code last_failure_instant} → {@code null}</li>
* </ul>
* Nicht geändert werden: {@code created_at}, {@code last_success_instant},
* {@code last_target_path}, {@code last_target_file_name} sowie alle
* {@code processing_attempt}-Einträge, die vollständig erhalten bleiben.
* <p>
* Nach diesem Aufruf gilt das Dokument beim nächsten Lauf als verarbeitbar.
*
* @param fingerprint der Dokumentbezeichner, dessen Status zurückgesetzt werden soll;
* darf nicht {@code null} sein
* @throws DocumentPersistenceException bei technischen Datenbankfehlern
*/
void resetDocumentStatusForRetry(DocumentFingerprint fingerprint);
}
}
@@ -0,0 +1,50 @@
package de.gecheckt.pdf.umbenenner.application.port.out.history;
import java.time.Instant;
import java.util.Objects;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
import de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus;
/**
* Einzelzeile der Dokumentenliste im Historien-Tab.
* <p>
* Enthält alle Felder, die für die linke Tabelle des Historien-Tabs benötigt werden.
* Die Felder stammen aus {@code document_record} und einem {@code COUNT}-Ausdruck über
* {@code processing_attempt}.
*
* @param fingerprint Inhalts-basierter Dokumentbezeichner; nie {@code null}
* @param overallStatus aktueller Gesamtstatus des Dokuments; nie {@code null}
* @param sourceFileName zuletzt bekannter Quelldateiname; nie {@code null}
* @param targetFileName zuletzt bekannter Zieldateiname; {@code null} falls noch kein
* erfolgreicher Lauf stattgefunden hat
* @param sourcePath zuletzt bekannter Quellpfad (opaker Locator-Wert); nie {@code null}
* @param updatedAt Zeitpunkt der letzten Aktualisierung des Stammsatzes; nie {@code null}
* @param attemptCount Anzahl historisierter Verarbeitungsversuche; immer &gt;= 0
*/
public record DocumentHistoryRow(
DocumentFingerprint fingerprint,
ProcessingStatus overallStatus,
String sourceFileName,
String targetFileName,
String sourcePath,
Instant updatedAt,
long attemptCount) {
/**
* Kompakter Konstruktor mit Pflichtfeldprüfung.
*
* @throws NullPointerException wenn ein Pflichtfeld {@code null} ist
* @throws IllegalArgumentException wenn {@code attemptCount} negativ ist
*/
public DocumentHistoryRow {
Objects.requireNonNull(fingerprint, "fingerprint darf nicht null sein");
Objects.requireNonNull(overallStatus, "overallStatus darf nicht null sein");
Objects.requireNonNull(sourceFileName, "sourceFileName darf nicht null sein");
Objects.requireNonNull(sourcePath, "sourcePath darf nicht null sein");
Objects.requireNonNull(updatedAt, "updatedAt darf nicht null sein");
if (attemptCount < 0) {
throw new IllegalArgumentException("attemptCount darf nicht negativ sein, war: " + attemptCount);
}
}
}
@@ -0,0 +1,65 @@
package de.gecheckt.pdf.umbenenner.application.port.out.history;
/**
* Abfrageparameter für den Historien-Tab.
* <p>
* Kapselt Freitextsuche, optionalen Status-Filter und das Limit der zurückzugebenden
* Zeilen. Das Limit ist bewusst auf 501 gesetzt, damit die aufrufende Schicht erkennen
* kann, ob mehr als 500 Treffer vorhanden sind.
*
* @param searchText optionaler Suchbegriff (Teilstring, case-insensitiv); {@code null}
* oder leer bedeutet keine Texteinschränkung
* @param statusFilter optionaler Status-Filter als Enum-Name; {@code null} bedeutet alle
* Status werden angezeigt
* @param limit maximale Anzahl zurückzugebender Zeilen; muss &gt;= 1 sein
*/
public record HistoryQuery(
String searchText,
String statusFilter,
int limit) {
/**
* Standard-Limit: 501 Zeilen abfragen, um bei Bedarf „mehr vorhanden" erkennen zu können.
*/
public static final int DEFAULT_LIMIT = 501;
/**
* Kompakter Konstruktor mit Pflichtfeldprüfung.
*
* @throws IllegalArgumentException wenn {@code limit} kleiner als 1 ist
*/
public HistoryQuery {
if (limit < 1) {
throw new IllegalArgumentException("limit muss mindestens 1 sein, war: " + limit);
}
}
/**
* Erzeugt eine Abfrage ohne Filter mit Standard-Limit.
*
* @return neue Abfrage ohne Einschränkungen
*/
public static HistoryQuery unfiltered() {
return new HistoryQuery(null, null, DEFAULT_LIMIT);
}
/**
* Erzeugt eine Abfrage mit Freitextsuche und Standard-Limit.
*
* @param searchText Suchbegriff; {@code null} oder leer bedeutet kein Filter
* @return neue Abfrage mit Textfilter
*/
public static HistoryQuery withSearchText(String searchText) {
return new HistoryQuery(searchText, null, DEFAULT_LIMIT);
}
/**
* Erzeugt eine Abfrage mit Status-Filter und Standard-Limit.
*
* @param statusFilter Enum-Name des gewünschten Status; {@code null} bedeutet kein Filter
* @return neue Abfrage mit Status-Filter
*/
public static HistoryQuery withStatus(String statusFilter) {
return new HistoryQuery(null, statusFilter, DEFAULT_LIMIT);
}
}
@@ -0,0 +1,61 @@
package de.gecheckt.pdf.umbenenner.application.port.out.history;
import java.util.List;
import java.util.Optional;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord;
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttempt;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
/**
* Outbound-Port für lesende Historien-Abfragen aus dem Historien-Tab.
* <p>
* Kapselt alle Datenbanklese-Operationen, die der Historien-Tab benötigt.
* Die Implementierung liegt ausschließlich in {@code pdf-umbenenner-adapter-out}.
* Die Application-Schicht kennt nur diesen Port-Vertrag keine JDBC-Typen.
*
* <h2>Architektur</h2>
* <p>
* Dieser Port ist bewusst von {@link de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecordRepository}
* und {@link de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttemptRepository}
* getrennt, damit die bestehenden Repositories nicht mit GUI-spezifischen Methoden
* aufgebläht werden.
*/
public interface HistoryQueryPort {
/**
* Lädt eine gefilterte und sortierte Übersicht aller Dokumenteneinträge.
* <p>
* Sortierung: {@code updated_at DESC, fingerprint ASC} (stabiler Tie-Breaker).
* Das in {@link HistoryQuery#limit()} angegebene Limit wird direkt als SQL-{@code LIMIT}
* angewendet. Wenn das Limit 501 beträgt und 501 Zeilen zurückgegeben werden, gibt es
* mehr als 500 Treffer.
*
* @param query Abfrageparameter mit Suchtext, Status-Filter und Limit; darf nicht {@code null} sein
* @return unveränderliche Liste der Trefferzeilen; nie {@code null}; kann leer sein
* @throws de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException bei
* technischen Datenbankfehlern
*/
List<DocumentHistoryRow> loadOverview(HistoryQuery query);
/**
* Lädt den vollständigen Dokumenten-Stammsatz für den angegebenen Fingerprint.
*
* @param fingerprint Dokumentbezeichner; darf nicht {@code null} sein
* @return Optional mit dem Stammsatz, oder leer wenn nicht vorhanden
* @throws de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException bei
* technischen Datenbankfehlern
*/
Optional<DocumentRecord> findRecordByFingerprint(DocumentFingerprint fingerprint);
/**
* Lädt alle historisierten Verarbeitungsversuche für den angegebenen Fingerprint,
* aufsteigend sortiert nach {@code attempt_number}.
*
* @param fingerprint Dokumentbezeichner; darf nicht {@code null} sein
* @return unveränderliche Liste der Versuche; nie {@code null}; kann leer sein
* @throws de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException bei
* technischen Datenbankfehlern
*/
List<ProcessingAttempt> findAttemptsByFingerprint(DocumentFingerprint fingerprint);
}
@@ -0,0 +1,12 @@
/**
* Outbound-Ports und DTOs für lesende Historien-Abfragen des Historien-Tabs.
* <p>
* Enthält den {@link de.gecheckt.pdf.umbenenner.application.port.out.history.HistoryQueryPort}
* sowie die zugehörigen Datentypen
* {@link de.gecheckt.pdf.umbenenner.application.port.out.history.HistoryQuery} und
* {@link de.gecheckt.pdf.umbenenner.application.port.out.history.DocumentHistoryRow}.
* Diese Typen sind bewusst vom bestehenden {@code port.out}-Paket getrennt,
* damit die allgemeinen Repository-Schnittstellen nicht mit GUI-spezifischen Methoden
* belastet werden.
*/
package de.gecheckt.pdf.umbenenner.application.port.out.history;
@@ -0,0 +1,65 @@
package de.gecheckt.pdf.umbenenner.application.usecase;
import java.util.Objects;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException;
import de.gecheckt.pdf.umbenenner.application.port.out.UnitOfWorkPort;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
/**
* Use-Case-Implementierung für das vollständige Löschen eines Dokumenteintrags
* aus dem Historien-Tab.
* <p>
* Löscht innerhalb einer Transaktion in der korrekten Reihenfolge, um den
* Foreign-Key-Constraint zwischen {@code processing_attempt.fingerprint} und
* {@code document_record.fingerprint} zu erfüllen (kein {@code ON DELETE CASCADE}):
* <ol>
* <li>Alle {@code processing_attempt}-Einträge zum Fingerprint</li>
* <li>Den {@code document_record}-Stammsatz zum Fingerprint</li>
* </ol>
* Die Operation ist idempotent: wenn kein Datensatz für den Fingerprint existiert,
* kehrt die Methode stillschweigend zurück.
* <p>
* <strong>Hinweis:</strong> Diese Aktion ist destruktiv und nicht rückgängig zu machen.
* Die GUI muss vor dem Aufruf einen Bestätigungsdialog anzeigen.
*/
public class DefaultDeleteDocumentHistoryUseCase {
private static final Logger logger = LogManager.getLogger(DefaultDeleteDocumentHistoryUseCase.class);
private final UnitOfWorkPort unitOfWorkPort;
/**
* Erzeugt den Use-Case mit dem erforderlichen Persistenz-Port.
*
* @param unitOfWorkPort Port für transaktionale Persistenzoperationen; darf nicht {@code null} sein
* @throws NullPointerException wenn {@code unitOfWorkPort} null ist
*/
public DefaultDeleteDocumentHistoryUseCase(UnitOfWorkPort unitOfWorkPort) {
this.unitOfWorkPort = Objects.requireNonNull(unitOfWorkPort, "unitOfWorkPort darf nicht null sein");
}
/**
* Löscht den Stammsatz und alle Verarbeitungsversuche für den angegebenen Fingerprint.
* <p>
* Die Löschung erfolgt in einer einzigen Transaktion. Versuche werden vor dem
* Stammsatz gelöscht, damit der Foreign-Key-Constraint eingehalten wird.
*
* @param fingerprint der Dokumentbezeichner, dessen Daten vollständig gelöscht werden sollen;
* darf nicht {@code null} sein
* @throws DocumentPersistenceException bei technischen Datenbankfehlern
* @throws NullPointerException wenn {@code fingerprint} null ist
*/
public void deleteHistory(DocumentFingerprint fingerprint) {
Objects.requireNonNull(fingerprint, "fingerprint darf nicht null sein");
// Nutzung der bestehenden Transaktion mit korrekter Löschreihenfolge:
// zuerst Versuche, dann Stammsatz (FK-Constraint)
unitOfWorkPort.executeInTransaction(tx -> tx.resetDocumentByFingerprint(fingerprint));
logger.info("Dokumenteintrag vollständig gelöscht für Fingerprint: {}", fingerprint.sha256Hex());
}
}
@@ -0,0 +1,74 @@
package de.gecheckt.pdf.umbenenner.application.usecase;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord;
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttempt;
import de.gecheckt.pdf.umbenenner.application.port.out.history.HistoryQueryPort;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
/**
* Use-Case-Implementierung für das Laden der Detailansicht eines Dokuments im Historien-Tab.
* <p>
* Kombiniert den Dokument-Stammsatz und alle historisierten Verarbeitungsversuche
* für einen bestimmten Fingerprint in einem einzigen Ergebnisobjekt.
* <p>
* Wird kein Stammsatz gefunden (z. B. weil das Dokument zwischenzeitlich gelöscht wurde),
* liefert {@link #loadDetails(DocumentFingerprint)} ein leeres {@link Optional}.
*/
public class DefaultHistoryDetailsUseCase {
private final HistoryQueryPort historyQueryPort;
/**
* Erzeugt den Use-Case mit dem erforderlichen Abfrage-Port.
*
* @param historyQueryPort Port für lesende Historienabfragen; darf nicht {@code null} sein
* @throws NullPointerException wenn {@code historyQueryPort} null ist
*/
public DefaultHistoryDetailsUseCase(HistoryQueryPort historyQueryPort) {
this.historyQueryPort = Objects.requireNonNull(historyQueryPort, "historyQueryPort darf nicht null sein");
}
/**
* Lädt den Stammsatz und alle Verarbeitungsversuche für den angegebenen Fingerprint.
*
* @param fingerprint Dokumentbezeichner; darf nicht {@code null} sein
* @return Optional mit den Detaildaten, oder leer wenn kein Stammsatz gefunden wurde
* @throws de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException
* bei technischen Datenbankfehlern
*/
public Optional<HistoryDetailsResult> loadDetails(DocumentFingerprint fingerprint) {
Objects.requireNonNull(fingerprint, "fingerprint darf nicht null sein");
Optional<DocumentRecord> record = historyQueryPort.findRecordByFingerprint(fingerprint);
if (record.isEmpty()) {
return Optional.empty();
}
List<ProcessingAttempt> attempts = historyQueryPort.findAttemptsByFingerprint(fingerprint);
return Optional.of(new HistoryDetailsResult(record.get(), attempts));
}
/**
* Ergebnis einer Historien-Detailabfrage.
*
* @param record Dokument-Stammsatz; nie {@code null}
* @param attempts alle historisierten Verarbeitungsversuche aufsteigend nach Versuchsnummer;
* nie {@code null}; kann leer sein
*/
public record HistoryDetailsResult(DocumentRecord record, List<ProcessingAttempt> attempts) {
/**
* Kompakter Konstruktor mit Pflichtfeldprüfung.
*
* @throws NullPointerException wenn {@code record} oder {@code attempts} null ist
*/
public HistoryDetailsResult {
Objects.requireNonNull(record, "record darf nicht null sein");
Objects.requireNonNull(attempts, "attempts darf nicht null sein");
}
}
}
@@ -0,0 +1,82 @@
package de.gecheckt.pdf.umbenenner.application.usecase;
import java.util.List;
import java.util.Objects;
import de.gecheckt.pdf.umbenenner.application.port.out.history.DocumentHistoryRow;
import de.gecheckt.pdf.umbenenner.application.port.out.history.HistoryQuery;
import de.gecheckt.pdf.umbenenner.application.port.out.history.HistoryQueryPort;
/**
* Use-Case-Implementierung für das Laden der Dokumentenliste im Historien-Tab.
* <p>
* Delegiert die Datenbankabfrage vollständig an {@link HistoryQueryPort} und
* wertet das LIMIT-501-Ergebnis aus, um der GUI signalisieren zu können, ob
* weitere Einträge vorhanden sind, die durch einen engeren Filter erreichbar wären.
* <p>
* <strong>LIMIT-501-Technik:</strong> Die Query wird mit {@code limit + 1 = 501}
* ausgeführt (sofern das übergebene Limit 500 beträgt). Wenn die Datenbank 501
* Zeilen zurückgibt, existieren mehr als 500 Treffer. Die zurückgegebene Liste
* enthält dann exakt 500 Zeilen (das letzte Element wird verworfen) und
* {@link HistoryOverviewResult#hasMore()} liefert {@code true}.
*/
public class DefaultHistoryOverviewUseCase {
private static final int MAX_DISPLAY_COUNT = 500;
private final HistoryQueryPort historyQueryPort;
/**
* Erzeugt den Use-Case mit dem erforderlichen Abfrage-Port.
*
* @param historyQueryPort Port für lesende Historienabfragen; darf nicht {@code null} sein
* @throws NullPointerException wenn {@code historyQueryPort} null ist
*/
public DefaultHistoryOverviewUseCase(HistoryQueryPort historyQueryPort) {
this.historyQueryPort = Objects.requireNonNull(historyQueryPort, "historyQueryPort darf nicht null sein");
}
/**
* Lädt die Dokumentenliste auf Basis der übergebenen Abfrageparameter.
* <p>
* Intern wird ein Limit von 501 verwendet, um erkennen zu können, ob mehr
* als 500 Treffer vorhanden sind.
*
* @param query Abfrageparameter mit Suchtext, Status-Filter und Limit; darf nicht {@code null} sein
* @return Ergebnisobjekt mit Trefferlist und {@code hasMore}-Flag; nie {@code null}
* @throws de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException
* bei technischen Datenbankfehlern
*/
public HistoryOverviewResult loadOverview(HistoryQuery query) {
Objects.requireNonNull(query, "query darf nicht null sein");
List<DocumentHistoryRow> rows = historyQueryPort.loadOverview(query);
if (rows.size() > MAX_DISPLAY_COUNT) {
// 501 Zeilen zurückgegeben: mehr als 500 Treffer vorhanden
List<DocumentHistoryRow> truncated = List.copyOf(rows.subList(0, MAX_DISPLAY_COUNT));
return new HistoryOverviewResult(truncated, true);
}
return new HistoryOverviewResult(List.copyOf(rows), false);
}
/**
* Ergebnis einer Historien-Übersichtsabfrage.
*
* @param rows Liste der Trefferzeilen; nie {@code null}; enthält maximal 500 Einträge
* @param hasMore {@code true}, wenn mehr als 500 Treffer vorhanden sind und durch
* einen engeren Filter eingegrenzt werden könnten
*/
public record HistoryOverviewResult(List<DocumentHistoryRow> rows, boolean hasMore) {
/**
* Kompakter Konstruktor mit Pflichtfeldprüfung.
*
* @throws NullPointerException wenn {@code rows} null ist
*/
public HistoryOverviewResult {
Objects.requireNonNull(rows, "rows darf nicht null sein");
}
}
}
@@ -0,0 +1,69 @@
package de.gecheckt.pdf.umbenenner.application.usecase;
import java.util.Objects;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException;
import de.gecheckt.pdf.umbenenner.application.port.out.UnitOfWorkPort;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
/**
* Use-Case-Implementierung für den feldgenauen Status-Reset aus dem Historien-Tab.
* <p>
* Setzt ausschließlich die vier fachlich relevanten Status-Felder zurück,
* ohne die Versuchshistorie zu löschen:
* <ul>
* <li>{@code overall_status} → {@code READY_FOR_AI}</li>
* <li>{@code content_error_count} → {@code 0}</li>
* <li>{@code transient_error_count} → {@code 0}</li>
* <li>{@code last_failure_instant} → {@code null}</li>
* </ul>
* Nicht geändert werden: {@code created_at}, {@code last_success_instant},
* {@code last_target_path}, {@code last_target_file_name} sowie alle
* {@code processing_attempt}-Einträge, die vollständig erhalten bleiben.
* <p>
* Nach dem Reset gilt das Dokument beim nächsten Verarbeitungslauf als verarbeitbar,
* da {@code READY_FOR_AI} der einzige Trigger für die Verarbeitungslogik ist.
* <p>
* <strong>Abgrenzung:</strong> Dieser Use-Case unterscheidet sich von
* {@link DefaultResetDocumentStatusUseCase}, der alle Persistenzdaten (Stammsatz und
* Versuchshistorie) vollständig löscht und das Dokument so behandelt, als wäre es
* noch nie verarbeitet worden.
*/
public class DefaultHistoryResetDocumentStatusUseCase {
private static final Logger logger = LogManager.getLogger(DefaultHistoryResetDocumentStatusUseCase.class);
private final UnitOfWorkPort unitOfWorkPort;
/**
* Erzeugt den Use-Case mit dem erforderlichen Persistenz-Port.
*
* @param unitOfWorkPort Port für transaktionale Persistenzoperationen; darf nicht {@code null} sein
* @throws NullPointerException wenn {@code unitOfWorkPort} null ist
*/
public DefaultHistoryResetDocumentStatusUseCase(UnitOfWorkPort unitOfWorkPort) {
this.unitOfWorkPort = Objects.requireNonNull(unitOfWorkPort, "unitOfWorkPort darf nicht null sein");
}
/**
* Führt den feldgenauen Status-Reset für den angegebenen Fingerprint durch.
* <p>
* Die Operation ist atomar: entweder werden alle vier Felder aktualisiert,
* oder keine Änderung findet statt (Rollback).
*
* @param fingerprint der Dokumentbezeichner, dessen Status zurückgesetzt werden soll;
* darf nicht {@code null} sein
* @throws DocumentPersistenceException bei technischen Datenbankfehlern
* @throws NullPointerException wenn {@code fingerprint} null ist
*/
public void resetStatus(DocumentFingerprint fingerprint) {
Objects.requireNonNull(fingerprint, "fingerprint darf nicht null sein");
unitOfWorkPort.executeInTransaction(tx -> tx.resetDocumentStatusForRetry(fingerprint));
logger.info("Feldgenauer Status-Reset durchgeführt für Fingerprint: {}", fingerprint.sha256Hex());
}
}
@@ -1416,6 +1416,11 @@ class DocumentProcessingCoordinatorTest {
public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) {
// No-op in tests
}
@Override
public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) {
// No-op in tests
}
};
operations.accept(mockOps);
@@ -1396,6 +1396,11 @@ class BatchRunProcessingUseCaseTest {
public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) {
// No-op
}
@Override
public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) {
// No-op
}
});
}
}
@@ -1604,6 +1609,11 @@ class BatchRunProcessingUseCaseTest {
public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) {
// No-op in tests
}
@Override
public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) {
// No-op in tests
}
});
}
}
@@ -0,0 +1,144 @@
package de.gecheckt.pdf.umbenenner.application.usecase;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNullPointerException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
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.ProcessingAttempt;
import de.gecheckt.pdf.umbenenner.application.port.out.UnitOfWorkPort;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
/**
* Tests für {@link DefaultDeleteDocumentHistoryUseCase}.
* <p>
* Prüft, dass ausschließlich {@code resetDocumentByFingerprint} aufgerufen wird
* (vollständige Löschung inklusive Versuchen, FK-sicher), Null-Guards greifen
* und Port-Fehler propagiert werden.
*/
class DefaultDeleteDocumentHistoryUseCaseTest {
private static final DocumentFingerprint FP =
new DocumentFingerprint("b".repeat(64));
// -------------------------------------------------------------------------
// Null-Guards
// -------------------------------------------------------------------------
@Test
void constructor_nullPort_throwsNPE() {
assertThatNullPointerException()
.isThrownBy(() -> new DefaultDeleteDocumentHistoryUseCase(null));
}
@Test
void deleteHistory_nullFingerprint_throwsNPE() {
DefaultDeleteDocumentHistoryUseCase useCase =
new DefaultDeleteDocumentHistoryUseCase(noOpPort());
assertThatNullPointerException()
.isThrownBy(() -> useCase.deleteHistory(null));
}
// -------------------------------------------------------------------------
// Happy path: vollständige Löschung
// -------------------------------------------------------------------------
@Test
void deleteHistory_callsResetDocumentByFingerprint() {
RecordingTransactionOperations ops = new RecordingTransactionOperations();
UnitOfWorkPort port = operations -> operations.accept(ops);
DefaultDeleteDocumentHistoryUseCase useCase =
new DefaultDeleteDocumentHistoryUseCase(port);
useCase.deleteHistory(FP);
assertThat(ops.resetByFingerprintFingerprints)
.containsExactly(FP);
}
@Test
void deleteHistory_doesNotCallResetDocumentStatusForRetry() {
RecordingTransactionOperations ops = new RecordingTransactionOperations();
UnitOfWorkPort port = operations -> operations.accept(ops);
DefaultDeleteDocumentHistoryUseCase useCase =
new DefaultDeleteDocumentHistoryUseCase(port);
useCase.deleteHistory(FP);
assertThat(ops.resetStatusForRetryFingerprints).isEmpty();
}
// -------------------------------------------------------------------------
// Port-Fehler wird propagiert
// -------------------------------------------------------------------------
@Test
void deleteHistory_portThrows_exceptionPropagated() {
UnitOfWorkPort failingPort = operations ->
operations.accept(new UnitOfWorkPort.TransactionOperations() {
@Override
public void saveProcessingAttempt(ProcessingAttempt attempt) { }
@Override
public void createDocumentRecord(DocumentRecord record) { }
@Override
public void updateDocumentRecord(DocumentRecord record) { }
@Override
public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) {
throw new DocumentPersistenceException("Simulated DB error");
}
@Override
public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) { }
});
DefaultDeleteDocumentHistoryUseCase useCase =
new DefaultDeleteDocumentHistoryUseCase(failingPort);
assertThatThrownBy(() -> useCase.deleteHistory(FP))
.isInstanceOf(DocumentPersistenceException.class)
.hasMessageContaining("Simulated DB error");
}
// -------------------------------------------------------------------------
// Hilfsmethoden
// -------------------------------------------------------------------------
private static UnitOfWorkPort noOpPort() {
return operations -> operations.accept(new UnitOfWorkPort.TransactionOperations() {
@Override public void saveProcessingAttempt(ProcessingAttempt a) { }
@Override public void createDocumentRecord(DocumentRecord r) { }
@Override public void updateDocumentRecord(DocumentRecord r) { }
@Override public void resetDocumentByFingerprint(DocumentFingerprint fp) { }
@Override public void resetDocumentStatusForRetry(DocumentFingerprint fp) { }
});
}
/**
* Zeichnet {@code resetDocumentByFingerprint}- und {@code resetDocumentStatusForRetry}-Aufrufe auf.
*/
private static class RecordingTransactionOperations
implements UnitOfWorkPort.TransactionOperations {
final List<DocumentFingerprint> resetByFingerprintFingerprints = new ArrayList<>();
final List<DocumentFingerprint> resetStatusForRetryFingerprints = new ArrayList<>();
@Override public void saveProcessingAttempt(ProcessingAttempt a) { }
@Override public void createDocumentRecord(DocumentRecord r) { }
@Override public void updateDocumentRecord(DocumentRecord r) { }
@Override
public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) {
resetByFingerprintFingerprints.add(fingerprint);
}
@Override
public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) {
resetStatusForRetryFingerprints.add(fingerprint);
}
}
}
@@ -0,0 +1,215 @@
package de.gecheckt.pdf.umbenenner.application.usecase;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNullPointerException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.time.Instant;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.Test;
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.FailureCounters;
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttempt;
import de.gecheckt.pdf.umbenenner.application.port.out.history.DocumentHistoryRow;
import de.gecheckt.pdf.umbenenner.application.port.out.history.HistoryQuery;
import de.gecheckt.pdf.umbenenner.application.port.out.history.HistoryQueryPort;
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultHistoryDetailsUseCase.HistoryDetailsResult;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
import de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;
/**
* Tests für {@link DefaultHistoryDetailsUseCase}.
* <p>
* Prüft den Happy-Path (Stammsatz vorhanden), das leere-Optional-Verhalten
* (kein Stammsatz), Null-Guards und Port-Fehler-Propagation.
*/
class DefaultHistoryDetailsUseCaseTest {
private static final DocumentFingerprint FP =
new DocumentFingerprint("a".repeat(64));
// -------------------------------------------------------------------------
// Null-Guards
// -------------------------------------------------------------------------
@Test
void constructor_nullPort_throwsNPE() {
assertThatNullPointerException()
.isThrownBy(() -> new DefaultHistoryDetailsUseCase(null));
}
@Test
void loadDetails_nullFingerprint_throwsNPE() {
DefaultHistoryDetailsUseCase useCase =
new DefaultHistoryDetailsUseCase(emptyPort());
assertThatNullPointerException()
.isThrownBy(() -> useCase.loadDetails(null));
}
// -------------------------------------------------------------------------
// Kein Stammsatz vorhanden
// -------------------------------------------------------------------------
@Test
void loadDetails_noRecord_returnsEmpty() {
DefaultHistoryDetailsUseCase useCase =
new DefaultHistoryDetailsUseCase(emptyPort());
Optional<HistoryDetailsResult> result = useCase.loadDetails(FP);
assertThat(result).isEmpty();
}
// -------------------------------------------------------------------------
// Happy path: Stammsatz vorhanden, Versuche vorhanden
// -------------------------------------------------------------------------
@Test
void loadDetails_recordExists_returnsResultWithRecordAndAttempts() {
DocumentRecord record = buildRecord(FP);
ProcessingAttempt attempt = buildAttempt(FP);
HistoryQueryPort port = new HistoryQueryPort() {
@Override
public List<DocumentHistoryRow> loadOverview(HistoryQuery query) {
return Collections.emptyList();
}
@Override
public Optional<DocumentRecord> findRecordByFingerprint(DocumentFingerprint fp) {
return Optional.of(record);
}
@Override
public List<ProcessingAttempt> findAttemptsByFingerprint(DocumentFingerprint fp) {
return List.of(attempt);
}
};
DefaultHistoryDetailsUseCase useCase = new DefaultHistoryDetailsUseCase(port);
Optional<HistoryDetailsResult> result = useCase.loadDetails(FP);
assertThat(result).isPresent();
assertThat(result.get().record()).isSameAs(record);
assertThat(result.get().attempts()).containsExactly(attempt);
}
// -------------------------------------------------------------------------
// Stammsatz vorhanden, keine Versuche
// -------------------------------------------------------------------------
@Test
void loadDetails_recordExistsNoAttempts_returnsResultWithEmptyAttempts() {
DocumentRecord record = buildRecord(FP);
HistoryQueryPort port = new HistoryQueryPort() {
@Override
public List<DocumentHistoryRow> loadOverview(HistoryQuery query) {
return Collections.emptyList();
}
@Override
public Optional<DocumentRecord> findRecordByFingerprint(DocumentFingerprint fp) {
return Optional.of(record);
}
@Override
public List<ProcessingAttempt> findAttemptsByFingerprint(DocumentFingerprint fp) {
return Collections.emptyList();
}
};
DefaultHistoryDetailsUseCase useCase = new DefaultHistoryDetailsUseCase(port);
Optional<HistoryDetailsResult> result = useCase.loadDetails(FP);
assertThat(result).isPresent();
assertThat(result.get().attempts()).isEmpty();
}
// -------------------------------------------------------------------------
// Port-Fehler wird propagiert
// -------------------------------------------------------------------------
@Test
void loadDetails_portThrowsOnRecord_exceptionPropagated() {
HistoryQueryPort failingPort = new HistoryQueryPort() {
@Override
public List<DocumentHistoryRow> loadOverview(HistoryQuery query) {
return Collections.emptyList();
}
@Override
public Optional<DocumentRecord> findRecordByFingerprint(DocumentFingerprint fp) {
throw new DocumentPersistenceException("Simulated DB error");
}
@Override
public List<ProcessingAttempt> findAttemptsByFingerprint(DocumentFingerprint fp) {
return Collections.emptyList();
}
};
DefaultHistoryDetailsUseCase useCase = new DefaultHistoryDetailsUseCase(failingPort);
assertThatThrownBy(() -> useCase.loadDetails(FP))
.isInstanceOf(DocumentPersistenceException.class)
.hasMessageContaining("Simulated DB error");
}
// -------------------------------------------------------------------------
// Hilfsmethoden
// -------------------------------------------------------------------------
private static HistoryQueryPort emptyPort() {
return new HistoryQueryPort() {
@Override
public List<DocumentHistoryRow> loadOverview(HistoryQuery query) {
return Collections.emptyList();
}
@Override
public Optional<DocumentRecord> findRecordByFingerprint(DocumentFingerprint fp) {
return Optional.empty();
}
@Override
public List<ProcessingAttempt> findAttemptsByFingerprint(DocumentFingerprint fp) {
return Collections.emptyList();
}
};
}
private static DocumentRecord buildRecord(DocumentFingerprint fp) {
return new DocumentRecord(
fp,
new SourceDocumentLocator("/source"),
"source.pdf",
ProcessingStatus.SUCCESS,
new FailureCounters(0, 0),
null,
Instant.now(),
Instant.now(),
Instant.now(),
"/target",
"2024-01-01 - Dokument.pdf");
}
private static ProcessingAttempt buildAttempt(DocumentFingerprint fp) {
return ProcessingAttempt.withoutAiFields(
fp,
new de.gecheckt.pdf.umbenenner.domain.model.RunId(
java.util.UUID.randomUUID().toString()),
1,
Instant.now(),
Instant.now(),
ProcessingStatus.SUCCESS,
null,
null,
false);
}
}
@@ -0,0 +1,199 @@
package de.gecheckt.pdf.umbenenner.application.usecase;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNullPointerException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.Test;
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.ProcessingAttempt;
import de.gecheckt.pdf.umbenenner.application.port.out.history.DocumentHistoryRow;
import de.gecheckt.pdf.umbenenner.application.port.out.history.HistoryQuery;
import de.gecheckt.pdf.umbenenner.application.port.out.history.HistoryQueryPort;
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultHistoryOverviewUseCase.HistoryOverviewResult;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
import de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus;
/**
* Tests für {@link DefaultHistoryOverviewUseCase}.
* <p>
* Prüft den Happy-Path, das LIMIT-501-Verhalten und Null-Guards.
*/
class DefaultHistoryOverviewUseCaseTest {
private static final DocumentFingerprint FP =
new DocumentFingerprint("a".repeat(64));
// -------------------------------------------------------------------------
// Null-Guards
// -------------------------------------------------------------------------
@Test
void constructor_nullPort_throwsNPE() {
assertThatNullPointerException()
.isThrownBy(() -> new DefaultHistoryOverviewUseCase(null));
}
@Test
void loadOverview_nullQuery_throwsNPE() {
DefaultHistoryOverviewUseCase useCase =
new DefaultHistoryOverviewUseCase(emptyPort());
assertThatNullPointerException()
.isThrownBy(() -> useCase.loadOverview(null));
}
// -------------------------------------------------------------------------
// Happy path: leer
// -------------------------------------------------------------------------
@Test
void loadOverview_emptyDatabase_returnsEmptyResultWithoutMore() {
DefaultHistoryOverviewUseCase useCase =
new DefaultHistoryOverviewUseCase(emptyPort());
HistoryOverviewResult result = useCase.loadOverview(HistoryQuery.unfiltered());
assertThat(result.rows()).isEmpty();
assertThat(result.hasMore()).isFalse();
}
// -------------------------------------------------------------------------
// Happy path: weniger als 500 Treffer
// -------------------------------------------------------------------------
@Test
void loadOverview_fewerThan500Results_returnsAllRowsWithoutMore() {
List<DocumentHistoryRow> rows = buildRows(10);
DefaultHistoryOverviewUseCase useCase =
new DefaultHistoryOverviewUseCase(fixedPort(rows));
HistoryOverviewResult result = useCase.loadOverview(HistoryQuery.unfiltered());
assertThat(result.rows()).hasSize(10);
assertThat(result.hasMore()).isFalse();
}
// -------------------------------------------------------------------------
// LIMIT-501-Technik
// -------------------------------------------------------------------------
@Test
void loadOverview_exactly500Results_returnsAllWithoutMore() {
List<DocumentHistoryRow> rows = buildRows(500);
DefaultHistoryOverviewUseCase useCase =
new DefaultHistoryOverviewUseCase(fixedPort(rows));
HistoryOverviewResult result = useCase.loadOverview(HistoryQuery.unfiltered());
assertThat(result.rows()).hasSize(500);
assertThat(result.hasMore()).isFalse();
}
@Test
void loadOverview_moreThan500Results_returns500RowsWithHasMore() {
List<DocumentHistoryRow> rows = buildRows(501);
DefaultHistoryOverviewUseCase useCase =
new DefaultHistoryOverviewUseCase(fixedPort(rows));
HistoryOverviewResult result = useCase.loadOverview(HistoryQuery.unfiltered());
assertThat(result.rows()).hasSize(500);
assertThat(result.hasMore()).isTrue();
}
@Test
void loadOverview_resultListIsImmutable() {
List<DocumentHistoryRow> rows = buildRows(3);
DefaultHistoryOverviewUseCase useCase =
new DefaultHistoryOverviewUseCase(fixedPort(rows));
HistoryOverviewResult result = useCase.loadOverview(HistoryQuery.unfiltered());
assertThatThrownBy(() -> result.rows().add(buildRow("0".repeat(64))))
.isInstanceOf(UnsupportedOperationException.class);
}
// -------------------------------------------------------------------------
// Port-Fehler wird propagiert
// -------------------------------------------------------------------------
@Test
void loadOverview_portThrows_exceptionPropagated() {
HistoryQueryPort failingPort = new HistoryQueryPort() {
@Override
public List<DocumentHistoryRow> loadOverview(HistoryQuery query) {
throw new DocumentPersistenceException("Simulated DB error");
}
@Override
public Optional<DocumentRecord> findRecordByFingerprint(DocumentFingerprint fp) {
return Optional.empty();
}
@Override
public List<ProcessingAttempt> findAttemptsByFingerprint(DocumentFingerprint fp) {
return Collections.emptyList();
}
};
DefaultHistoryOverviewUseCase useCase = new DefaultHistoryOverviewUseCase(failingPort);
assertThatThrownBy(() -> useCase.loadOverview(HistoryQuery.unfiltered()))
.isInstanceOf(DocumentPersistenceException.class)
.hasMessageContaining("Simulated DB error");
}
// -------------------------------------------------------------------------
// Hilfsmethoden
// -------------------------------------------------------------------------
private static HistoryQueryPort emptyPort() {
return fixedPort(Collections.emptyList());
}
private static HistoryQueryPort fixedPort(List<DocumentHistoryRow> rows) {
return new HistoryQueryPort() {
@Override
public List<DocumentHistoryRow> loadOverview(HistoryQuery query) {
return new ArrayList<>(rows);
}
@Override
public Optional<DocumentRecord> findRecordByFingerprint(DocumentFingerprint fp) {
return Optional.empty();
}
@Override
public List<ProcessingAttempt> findAttemptsByFingerprint(DocumentFingerprint fp) {
return Collections.emptyList();
}
};
}
private static List<DocumentHistoryRow> buildRows(int count) {
List<DocumentHistoryRow> result = new ArrayList<>();
for (int i = 0; i < count; i++) {
String hex = String.format("%064x", i);
result.add(buildRow(hex));
}
return result;
}
private static DocumentHistoryRow buildRow(String fpHex) {
return new DocumentHistoryRow(
new DocumentFingerprint(fpHex),
ProcessingStatus.SUCCESS,
"source.pdf",
"2024-01-01 - Dokument.pdf",
"/source",
Instant.now(),
1L);
}
}
@@ -0,0 +1,147 @@
package de.gecheckt.pdf.umbenenner.application.usecase;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNullPointerException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
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.ProcessingAttempt;
import de.gecheckt.pdf.umbenenner.application.port.out.UnitOfWorkPort;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
/**
* Tests für {@link DefaultHistoryResetDocumentStatusUseCase}.
* <p>
* Prüft, dass ausschließlich {@code resetDocumentStatusForRetry} aufgerufen wird
* (nicht {@code resetDocumentByFingerprint}), Null-Guards greifen und
* Port-Fehler propagiert werden.
*/
class DefaultHistoryResetDocumentStatusUseCaseTest {
private static final DocumentFingerprint FP =
new DocumentFingerprint("a".repeat(64));
// -------------------------------------------------------------------------
// Null-Guards
// -------------------------------------------------------------------------
@Test
void constructor_nullPort_throwsNPE() {
assertThatNullPointerException()
.isThrownBy(() -> new DefaultHistoryResetDocumentStatusUseCase(null));
}
@Test
void resetStatus_nullFingerprint_throwsNPE() {
DefaultHistoryResetDocumentStatusUseCase useCase =
new DefaultHistoryResetDocumentStatusUseCase(noOpPort());
assertThatNullPointerException()
.isThrownBy(() -> useCase.resetStatus(null));
}
// -------------------------------------------------------------------------
// Happy path: feldgenauer Reset
// -------------------------------------------------------------------------
@Test
void resetStatus_callsResetDocumentStatusForRetry() {
RecordingTransactionOperations ops = new RecordingTransactionOperations();
UnitOfWorkPort port = operations -> operations.accept(ops);
DefaultHistoryResetDocumentStatusUseCase useCase =
new DefaultHistoryResetDocumentStatusUseCase(port);
useCase.resetStatus(FP);
assertThat(ops.resetStatusForRetryFingerprints)
.containsExactly(FP);
assertThat(ops.resetByFingerprintFingerprints)
.isEmpty();
}
@Test
void resetStatus_doesNotCallResetDocumentByFingerprint() {
RecordingTransactionOperations ops = new RecordingTransactionOperations();
UnitOfWorkPort port = operations -> operations.accept(ops);
DefaultHistoryResetDocumentStatusUseCase useCase =
new DefaultHistoryResetDocumentStatusUseCase(port);
useCase.resetStatus(FP);
assertThat(ops.resetByFingerprintFingerprints).isEmpty();
}
// -------------------------------------------------------------------------
// Port-Fehler wird propagiert
// -------------------------------------------------------------------------
@Test
void resetStatus_portThrows_exceptionPropagated() {
UnitOfWorkPort failingPort = operations ->
operations.accept(new UnitOfWorkPort.TransactionOperations() {
@Override
public void saveProcessingAttempt(ProcessingAttempt attempt) { }
@Override
public void createDocumentRecord(DocumentRecord record) { }
@Override
public void updateDocumentRecord(DocumentRecord record) { }
@Override
public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) { }
@Override
public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) {
throw new DocumentPersistenceException("Simulated DB error");
}
});
DefaultHistoryResetDocumentStatusUseCase useCase =
new DefaultHistoryResetDocumentStatusUseCase(failingPort);
assertThatThrownBy(() -> useCase.resetStatus(FP))
.isInstanceOf(DocumentPersistenceException.class)
.hasMessageContaining("Simulated DB error");
}
// -------------------------------------------------------------------------
// Hilfsmethoden
// -------------------------------------------------------------------------
private static UnitOfWorkPort noOpPort() {
return operations -> operations.accept(new UnitOfWorkPort.TransactionOperations() {
@Override public void saveProcessingAttempt(ProcessingAttempt a) { }
@Override public void createDocumentRecord(DocumentRecord r) { }
@Override public void updateDocumentRecord(DocumentRecord r) { }
@Override public void resetDocumentByFingerprint(DocumentFingerprint fp) { }
@Override public void resetDocumentStatusForRetry(DocumentFingerprint fp) { }
});
}
/**
* Zeichnet {@code resetDocumentStatusForRetry}- und {@code resetDocumentByFingerprint}-Aufrufe auf.
*/
private static class RecordingTransactionOperations
implements UnitOfWorkPort.TransactionOperations {
final List<DocumentFingerprint> resetStatusForRetryFingerprints = new ArrayList<>();
final List<DocumentFingerprint> resetByFingerprintFingerprints = new ArrayList<>();
@Override public void saveProcessingAttempt(ProcessingAttempt a) { }
@Override public void createDocumentRecord(DocumentRecord r) { }
@Override public void updateDocumentRecord(DocumentRecord r) { }
@Override
public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) {
resetByFingerprintFingerprints.add(fingerprint);
}
@Override
public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) {
resetStatusForRetryFingerprints.add(fingerprint);
}
}
}
@@ -549,6 +549,7 @@ class DefaultManualFileCopyUseCaseTest {
@Override public void createDocumentRecord(DocumentRecord record) { }
@Override public void updateDocumentRecord(DocumentRecord record) { }
@Override public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) { }
@Override public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) { }
}
private static class RecordCapturingTransactionOperations implements UnitOfWorkPort.TransactionOperations {
@@ -562,5 +563,6 @@ class DefaultManualFileCopyUseCaseTest {
@Override public void createDocumentRecord(DocumentRecord record) { }
@Override public void updateDocumentRecord(DocumentRecord record) { captured.add(record); }
@Override public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) { }
@Override public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) { }
}
}
@@ -620,6 +620,7 @@ class DefaultManualFileRenameUseCaseTest {
@Override public void createDocumentRecord(DocumentRecord record) { }
@Override public void updateDocumentRecord(DocumentRecord record) { }
@Override public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) { }
@Override public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) { }
}
/** Zeichnet updateDocumentRecord-Aufrufe auf. */
@@ -634,5 +635,6 @@ class DefaultManualFileRenameUseCaseTest {
@Override public void createDocumentRecord(DocumentRecord record) { }
@Override public void updateDocumentRecord(DocumentRecord record) { captured.add(record); }
@Override public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) { }
@Override public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) { }
}
}
@@ -216,5 +216,10 @@ class DefaultResetDocumentStatusUseCaseTest {
public void resetDocumentByFingerprint(DocumentFingerprint fingerprint) {
recorded.add(fingerprint);
}
@Override
public void resetDocumentStatusForRetry(DocumentFingerprint fingerprint) {
// No-op in tests
}
}
}