Feature: Scheduler-Tick-Zaehlung korrigieren und Sitzungstotale einfuehren
Der Scheduler-Tab meldete nach erfolgreicher Verarbeitung faelschlich "keine neuen Dokumente". Ursache war ein hartkodiertes RunSummary.noOp() im BatchRunTrigger der Bootstrap; der echte Lauf-Summary wurde nie gelesen. - Bootstrap: BatchRunProgressObserver erfasst RunSummary aus onRunEnded und uebersetzt ihn in den ausgehenden RunSummary fuer das Tick-Ergebnis - Neuer Wert-Typ SchedulerSessionTotals (success/failed) plus Optional-Feld in SchedulerStatus - DefaultSchedulerControlUseCase setzt die Totale beim start() auf null zurueck, summiert pro Started-Tick auf, friert sie beim stop() ein - GuiSchedulerTab zeigt pro Tick "X verarbeitet, Y Fehler" oder "keine neuen Dokumente" sowie ein zusaetzliches Label "Seit Scheduler-Start: X verarbeitet, Y Fehler" Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+27
-3
@@ -13,6 +13,7 @@ import org.apache.logging.log4j.LogManager;
|
|||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerControlUseCase;
|
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerControlUseCase;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerSessionTotals;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerStartException;
|
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerStartException;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerState;
|
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerState;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerStatus;
|
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerStatus;
|
||||||
@@ -81,6 +82,7 @@ public final class GuiSchedulerTab {
|
|||||||
private final Button stopButton = new Button("Scheduler stoppen");
|
private final Button stopButton = new Button("Scheduler stoppen");
|
||||||
private final Label nextTickLabel = new Label();
|
private final Label nextTickLabel = new Label();
|
||||||
private final Label lastRunLabel = new Label("Noch kein Lauf in dieser Sitzung.");
|
private final Label lastRunLabel = new Label("Noch kein Lauf in dieser Sitzung.");
|
||||||
|
private final Label sessionTotalsLabel = new Label();
|
||||||
private final Label lastErrorLabel = new Label();
|
private final Label lastErrorLabel = new Label();
|
||||||
private final TextField intervalField = new TextField();
|
private final TextField intervalField = new TextField();
|
||||||
private final Label intervalValidationLabel = new Label();
|
private final Label intervalValidationLabel = new Label();
|
||||||
@@ -174,6 +176,7 @@ public final class GuiSchedulerTab {
|
|||||||
updateButtons(status);
|
updateButtons(status);
|
||||||
updateNextTickLabel(status);
|
updateNextTickLabel(status);
|
||||||
updateLastRunLabel(status);
|
updateLastRunLabel(status);
|
||||||
|
updateSessionTotalsLabel(status);
|
||||||
updateLastErrorLabel(status);
|
updateLastErrorLabel(status);
|
||||||
updateIntervalFieldEditability(status);
|
updateIntervalFieldEditability(status);
|
||||||
}
|
}
|
||||||
@@ -199,6 +202,11 @@ public final class GuiSchedulerTab {
|
|||||||
|
|
||||||
lastRunLabel.setWrapText(true);
|
lastRunLabel.setWrapText(true);
|
||||||
|
|
||||||
|
sessionTotalsLabel.setWrapText(true);
|
||||||
|
sessionTotalsLabel.setStyle("-fx-text-fill: #7f8c8d;");
|
||||||
|
sessionTotalsLabel.setVisible(false);
|
||||||
|
sessionTotalsLabel.setManaged(false);
|
||||||
|
|
||||||
lastErrorLabel.setStyle("-fx-text-fill: #c0392b;");
|
lastErrorLabel.setStyle("-fx-text-fill: #c0392b;");
|
||||||
lastErrorLabel.setWrapText(true);
|
lastErrorLabel.setWrapText(true);
|
||||||
lastErrorLabel.setVisible(false);
|
lastErrorLabel.setVisible(false);
|
||||||
@@ -219,6 +227,7 @@ public final class GuiSchedulerTab {
|
|||||||
buttonBox,
|
buttonBox,
|
||||||
nextTickLabel,
|
nextTickLabel,
|
||||||
lastRunLabel,
|
lastRunLabel,
|
||||||
|
sessionTotalsLabel,
|
||||||
lastErrorLabel,
|
lastErrorLabel,
|
||||||
new Separator(),
|
new Separator(),
|
||||||
intervalBox,
|
intervalBox,
|
||||||
@@ -337,19 +346,34 @@ public final class GuiSchedulerTab {
|
|||||||
RunSummary summary = status.lastRunSummary().get();
|
RunSummary summary = status.lastRunSummary().get();
|
||||||
String timeStr = TIME_FORMATTER.format(endedAt);
|
String timeStr = TIME_FORMATTER.format(endedAt);
|
||||||
boolean noDocuments = summary.successCount() == 0
|
boolean noDocuments = summary.successCount() == 0
|
||||||
&& summary.failedCount() == 0
|
&& summary.failedCount() == 0;
|
||||||
&& summary.skippedCount() == 0;
|
|
||||||
if (noDocuments) {
|
if (noDocuments) {
|
||||||
lastRunLabel.setText("Letzter Lauf: " + timeStr + " – keine neuen Dokumente");
|
lastRunLabel.setText("Letzter Lauf: " + timeStr + " – keine neuen Dokumente");
|
||||||
} else {
|
} else {
|
||||||
lastRunLabel.setText("Letzter Lauf: " + timeStr + " – "
|
lastRunLabel.setText("Letzter Lauf: " + timeStr + " – "
|
||||||
+ summary.successCount() + " Dokumente verarbeitet");
|
+ summary.successCount() + " verarbeitet, "
|
||||||
|
+ summary.failedCount() + " Fehler");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lastRunLabel.setText("Noch kein Lauf in dieser Sitzung.");
|
lastRunLabel.setText("Noch kein Lauf in dieser Sitzung.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateSessionTotalsLabel(SchedulerStatus status) {
|
||||||
|
Optional<SchedulerSessionTotals> totals = status.sessionTotals();
|
||||||
|
if (totals.isPresent()) {
|
||||||
|
SchedulerSessionTotals t = totals.get();
|
||||||
|
sessionTotalsLabel.setText("Seit Scheduler-Start: "
|
||||||
|
+ t.successCount() + " verarbeitet, "
|
||||||
|
+ t.failedCount() + " Fehler");
|
||||||
|
sessionTotalsLabel.setVisible(true);
|
||||||
|
sessionTotalsLabel.setManaged(true);
|
||||||
|
} else {
|
||||||
|
sessionTotalsLabel.setVisible(false);
|
||||||
|
sessionTotalsLabel.setManaged(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void updateLastErrorLabel(SchedulerStatus status) {
|
private void updateLastErrorLabel(SchedulerStatus status) {
|
||||||
Optional<String> lastError = status.lastError();
|
Optional<String> lastError = status.lastError();
|
||||||
if (lastError.isPresent() && !lastError.get().isBlank()) {
|
if (lastError.isPresent() && !lastError.get().isBlank()) {
|
||||||
|
|||||||
+58
@@ -0,0 +1,58 @@
|
|||||||
|
package de.gecheckt.pdf.umbenenner.application.port.in;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aggregierte Zähler über alle abgeschlossenen Ticks der laufenden bzw. zuletzt
|
||||||
|
* gelaufenen Scheduler-Sitzung.
|
||||||
|
* <p>
|
||||||
|
* Eine Sitzung beginnt mit dem nächsten erfolgreichen
|
||||||
|
* {@link SchedulerControlUseCase#start()} und endet mit dem zugehörigen
|
||||||
|
* {@link SchedulerControlUseCase#stop()}. Beim Start einer neuen Sitzung
|
||||||
|
* werden die Zähler auf null zurückgesetzt; nach dem Stopp bleiben sie
|
||||||
|
* eingefroren sichtbar, bis der Scheduler erneut gestartet wird.
|
||||||
|
* <p>
|
||||||
|
* Übersprungene Dokumente werden in dieser Sitzungsstatistik bewusst nicht
|
||||||
|
* gezählt, da sie für den Bediener keine neue Verarbeitungsleistung darstellen.
|
||||||
|
*
|
||||||
|
* @param successCount Summe aller erfolgreich verarbeiteten Dokumente seit
|
||||||
|
* Sitzungsstart; nie negativ
|
||||||
|
* @param failedCount Summe aller fehlgeschlagenen Dokumente seit Sitzungsstart
|
||||||
|
* (retryable und final zusammengefasst); nie negativ
|
||||||
|
*/
|
||||||
|
public record SchedulerSessionTotals(int successCount, int failedCount) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validiert, dass beide Zähler nicht negativ sind.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException wenn einer der Zähler kleiner als null ist
|
||||||
|
*/
|
||||||
|
public SchedulerSessionTotals {
|
||||||
|
if (successCount < 0 || failedCount < 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"SchedulerSessionTotals counts must not be negative; was: "
|
||||||
|
+ successCount + "/" + failedCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert ein neutrales Ausgangsobjekt mit beiden Zählern auf null.
|
||||||
|
*
|
||||||
|
* @return Sitzungstotal mit allen Zählern auf null; nie {@code null}
|
||||||
|
*/
|
||||||
|
public static SchedulerSessionTotals zero() {
|
||||||
|
return new SchedulerSessionTotals(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert ein neues Sitzungstotal, in dem die übergebenen Werte additiv
|
||||||
|
* aufgenommen wurden. Das aktuelle Objekt bleibt unverändert.
|
||||||
|
*
|
||||||
|
* @param additionalSuccess hinzuzurechnende Erfolgreich-Zahl; muss ≥ 0 sein
|
||||||
|
* @param additionalFailed hinzuzurechnende Fehler-Zahl; muss ≥ 0 sein
|
||||||
|
* @return aufaddiertes Sitzungstotal; nie {@code null}
|
||||||
|
*/
|
||||||
|
public SchedulerSessionTotals plus(int additionalSuccess, int additionalFailed) {
|
||||||
|
return new SchedulerSessionTotals(
|
||||||
|
successCount + additionalSuccess,
|
||||||
|
failedCount + additionalFailed);
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-1
@@ -26,13 +26,19 @@ import de.gecheckt.pdf.umbenenner.application.port.out.RunSummary;
|
|||||||
* @param lastError letzte aufgetretene deutsche Fehlermeldung;
|
* @param lastError letzte aufgetretene deutsche Fehlermeldung;
|
||||||
* wird bei erfolgreichem Lauf gelöscht,
|
* wird bei erfolgreichem Lauf gelöscht,
|
||||||
* bei {@code SkippedBusy} unverändert gelassen
|
* bei {@code SkippedBusy} unverändert gelassen
|
||||||
|
* @param sessionTotals aggregierte Zähler seit dem letzten Sitzungsstart;
|
||||||
|
* leer vor dem allerersten {@code start()}, ab dem
|
||||||
|
* ersten erfolgreichen Start gefüllt und bei jedem
|
||||||
|
* weiteren Start auf null zurückgesetzt; nach dem
|
||||||
|
* Stopp bleibt der eingefrorene Endwert sichtbar
|
||||||
*/
|
*/
|
||||||
public record SchedulerStatus(
|
public record SchedulerStatus(
|
||||||
SchedulerState state,
|
SchedulerState state,
|
||||||
Optional<Instant> lastRunEndedAt,
|
Optional<Instant> lastRunEndedAt,
|
||||||
Optional<RunSummary> lastRunSummary,
|
Optional<RunSummary> lastRunSummary,
|
||||||
Optional<Instant> nextTickAt,
|
Optional<Instant> nextTickAt,
|
||||||
Optional<String> lastError
|
Optional<String> lastError,
|
||||||
|
Optional<SchedulerSessionTotals> sessionTotals
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -54,6 +60,9 @@ public record SchedulerStatus(
|
|||||||
if (lastError == null) {
|
if (lastError == null) {
|
||||||
throw new IllegalArgumentException("lastError darf nicht null sein");
|
throw new IllegalArgumentException("lastError darf nicht null sein");
|
||||||
}
|
}
|
||||||
|
if (sessionTotals == null) {
|
||||||
|
throw new IllegalArgumentException("sessionTotals darf nicht null sein");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -70,6 +79,7 @@ public record SchedulerStatus(
|
|||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
Optional.empty()
|
Optional.empty()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-4
@@ -1,6 +1,7 @@
|
|||||||
package de.gecheckt.pdf.umbenenner.application.usecase;
|
package de.gecheckt.pdf.umbenenner.application.usecase;
|
||||||
|
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerControlUseCase;
|
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerControlUseCase;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerSessionTotals;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerStartException;
|
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerStartException;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerState;
|
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerState;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerStatus;
|
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerStatus;
|
||||||
@@ -132,9 +133,15 @@ public class DefaultSchedulerControlUseCase implements SchedulerControlUseCase {
|
|||||||
"Scheduler-Adapter konnte nicht gestartet werden.", e);
|
"Scheduler-Adapter konnte nicht gestartet werden.", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schritt 4: Zustand auf RUNNING_IDLE setzen
|
// Schritt 4: Zustand auf RUNNING_IDLE setzen, Sitzungstotal auf null zurücksetzen
|
||||||
Instant nextTick = Instant.now().plusSeconds(intervalSeconds);
|
Instant nextTick = Instant.now().plusSeconds(intervalSeconds);
|
||||||
statusRef.updateAndGet(s -> withState(s, SchedulerState.RUNNING_IDLE, Optional.of(nextTick)));
|
statusRef.updateAndGet(s -> new SchedulerStatus(
|
||||||
|
SchedulerState.RUNNING_IDLE,
|
||||||
|
s.lastRunEndedAt(),
|
||||||
|
s.lastRunSummary(),
|
||||||
|
Optional.of(nextTick),
|
||||||
|
s.lastError(),
|
||||||
|
Optional.of(SchedulerSessionTotals.zero())));
|
||||||
logger.info("Scheduler gestartet. Intervall: {} Sekunden.", intervalSeconds);
|
logger.info("Scheduler gestartet. Intervall: {} Sekunden.", intervalSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,12 +254,17 @@ public class DefaultSchedulerControlUseCase implements SchedulerControlUseCase {
|
|||||||
Optional<Instant> lastRunEndedAt = afterBatch.lastRunEndedAt();
|
Optional<Instant> lastRunEndedAt = afterBatch.lastRunEndedAt();
|
||||||
Optional<RunSummary> lastRunSummary = afterBatch.lastRunSummary();
|
Optional<RunSummary> lastRunSummary = afterBatch.lastRunSummary();
|
||||||
Optional<String> lastError = afterBatch.lastError();
|
Optional<String> lastError = afterBatch.lastError();
|
||||||
|
Optional<SchedulerSessionTotals> sessionTotals = afterBatch.sessionTotals();
|
||||||
|
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case BatchRunTriggerResult.Started started -> {
|
case BatchRunTriggerResult.Started started -> {
|
||||||
lastRunEndedAt = Optional.of(started.endedAt());
|
lastRunEndedAt = Optional.of(started.endedAt());
|
||||||
lastRunSummary = Optional.of(started.summary());
|
lastRunSummary = Optional.of(started.summary());
|
||||||
lastError = Optional.empty();
|
lastError = Optional.empty();
|
||||||
|
sessionTotals = Optional.of(sessionTotals
|
||||||
|
.orElse(SchedulerSessionTotals.zero())
|
||||||
|
.plus(started.summary().successCount(),
|
||||||
|
started.summary().failedCount()));
|
||||||
logger.info("Scheduler-Tick abgeschlossen: {} erfolgreich, {} fehlgeschlagen, {} übersprungen.",
|
logger.info("Scheduler-Tick abgeschlossen: {} erfolgreich, {} fehlgeschlagen, {} übersprungen.",
|
||||||
started.summary().successCount(),
|
started.summary().successCount(),
|
||||||
started.summary().failedCount(),
|
started.summary().failedCount(),
|
||||||
@@ -271,7 +283,8 @@ public class DefaultSchedulerControlUseCase implements SchedulerControlUseCase {
|
|||||||
lastRunEndedAt,
|
lastRunEndedAt,
|
||||||
lastRunSummary,
|
lastRunSummary,
|
||||||
nextTickAt,
|
nextTickAt,
|
||||||
lastError));
|
lastError,
|
||||||
|
sessionTotals));
|
||||||
|
|
||||||
if (stopping) {
|
if (stopping) {
|
||||||
lockPort.releaseLock();
|
lockPort.releaseLock();
|
||||||
@@ -292,6 +305,7 @@ public class DefaultSchedulerControlUseCase implements SchedulerControlUseCase {
|
|||||||
base.lastRunEndedAt(),
|
base.lastRunEndedAt(),
|
||||||
base.lastRunSummary(),
|
base.lastRunSummary(),
|
||||||
nextTickAt,
|
nextTickAt,
|
||||||
base.lastError());
|
base.lastError(),
|
||||||
|
base.sessionTotals());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+88
@@ -19,6 +19,7 @@ import org.mockito.ArgumentCaptor;
|
|||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerSessionTotals;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerStartException;
|
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerStartException;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerState;
|
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerState;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerStatus;
|
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerStatus;
|
||||||
@@ -63,6 +64,93 @@ class DefaultSchedulerControlUseCaseTest {
|
|||||||
assertThat(status.lastRunSummary()).isEmpty();
|
assertThat(status.lastRunSummary()).isEmpty();
|
||||||
assertThat(status.nextTickAt()).isEmpty();
|
assertThat(status.nextTickAt()).isEmpty();
|
||||||
assertThat(status.lastError()).isEmpty();
|
assertThat(status.lastError()).isEmpty();
|
||||||
|
assertThat(status.sessionTotals()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void start_initializesSessionTotalsToZero() {
|
||||||
|
DefaultSchedulerControlUseCase useCase = createUseCase();
|
||||||
|
|
||||||
|
useCase.start();
|
||||||
|
|
||||||
|
SchedulerStatus status = useCase.getStatus();
|
||||||
|
assertThat(status.sessionTotals()).contains(SchedulerSessionTotals.zero());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void tick_started_accumulatesIntoSessionTotals() {
|
||||||
|
DefaultSchedulerControlUseCase useCase = createUseCase();
|
||||||
|
useCase.start();
|
||||||
|
when(batchRunTrigger.triggerRun())
|
||||||
|
.thenReturn(new BatchRunTriggerResult.Started(Instant.now(), new RunSummary(2, 1, 4)))
|
||||||
|
.thenReturn(new BatchRunTriggerResult.Started(Instant.now(), new RunSummary(3, 0, 0)));
|
||||||
|
|
||||||
|
useCase.executeWrappedTick();
|
||||||
|
useCase.executeWrappedTick();
|
||||||
|
|
||||||
|
SchedulerStatus status = useCase.getStatus();
|
||||||
|
assertThat(status.sessionTotals()).contains(new SchedulerSessionTotals(5, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void tick_skippedBusy_doesNotChangeSessionTotals() {
|
||||||
|
DefaultSchedulerControlUseCase useCase = createUseCase();
|
||||||
|
useCase.start();
|
||||||
|
when(batchRunTrigger.triggerRun())
|
||||||
|
.thenReturn(new BatchRunTriggerResult.Started(Instant.now(), new RunSummary(2, 1, 0)))
|
||||||
|
.thenReturn(new BatchRunTriggerResult.SkippedBusy());
|
||||||
|
|
||||||
|
useCase.executeWrappedTick();
|
||||||
|
useCase.executeWrappedTick();
|
||||||
|
|
||||||
|
SchedulerStatus status = useCase.getStatus();
|
||||||
|
assertThat(status.sessionTotals()).contains(new SchedulerSessionTotals(2, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void tick_failed_doesNotChangeSessionTotals() {
|
||||||
|
DefaultSchedulerControlUseCase useCase = createUseCase();
|
||||||
|
useCase.start();
|
||||||
|
when(batchRunTrigger.triggerRun())
|
||||||
|
.thenReturn(new BatchRunTriggerResult.Started(Instant.now(), new RunSummary(1, 1, 0)))
|
||||||
|
.thenReturn(new BatchRunTriggerResult.Failed("Fehler", "tech"));
|
||||||
|
|
||||||
|
useCase.executeWrappedTick();
|
||||||
|
useCase.executeWrappedTick();
|
||||||
|
|
||||||
|
SchedulerStatus status = useCase.getStatus();
|
||||||
|
assertThat(status.sessionTotals()).contains(new SchedulerSessionTotals(1, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void stop_keepsSessionTotalsVisible() {
|
||||||
|
DefaultSchedulerControlUseCase useCase = createUseCase();
|
||||||
|
useCase.start();
|
||||||
|
when(batchRunTrigger.triggerRun())
|
||||||
|
.thenReturn(new BatchRunTriggerResult.Started(Instant.now(), new RunSummary(7, 2, 0)));
|
||||||
|
useCase.executeWrappedTick();
|
||||||
|
|
||||||
|
useCase.stop();
|
||||||
|
|
||||||
|
SchedulerStatus status = useCase.getStatus();
|
||||||
|
assertThat(status.state()).isEqualTo(SchedulerState.STOPPED);
|
||||||
|
assertThat(status.sessionTotals()).contains(new SchedulerSessionTotals(7, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void start_afterPreviousSession_resetsSessionTotalsToZero() {
|
||||||
|
DefaultSchedulerControlUseCase useCase = createUseCase();
|
||||||
|
useCase.start();
|
||||||
|
when(batchRunTrigger.triggerRun())
|
||||||
|
.thenReturn(new BatchRunTriggerResult.Started(Instant.now(), new RunSummary(5, 3, 0)));
|
||||||
|
useCase.executeWrappedTick();
|
||||||
|
useCase.stop();
|
||||||
|
assertThat(useCase.getStatus().sessionTotals())
|
||||||
|
.contains(new SchedulerSessionTotals(5, 3));
|
||||||
|
|
||||||
|
useCase.start();
|
||||||
|
|
||||||
|
assertThat(useCase.getStatus().sessionTotals()).contains(SchedulerSessionTotals.zero());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
+31
-2
@@ -1094,16 +1094,45 @@ public class BootstrapRunner {
|
|||||||
RunLockPort runLockPort = runLockPortFactory.create(
|
RunLockPort runLockPort = runLockPortFactory.create(
|
||||||
resolveLockFilePath(ctx.startConfiguration()));
|
resolveLockFilePath(ctx.startConfiguration()));
|
||||||
BatchRunContext runContext = createRunContext();
|
BatchRunContext runContext = createRunContext();
|
||||||
|
java.util.concurrent.atomic.AtomicReference<de.gecheckt.pdf.umbenenner.application.port.in.RunSummary>
|
||||||
|
capturedSummary = new java.util.concurrent.atomic.AtomicReference<>();
|
||||||
|
de.gecheckt.pdf.umbenenner.application.port.in.BatchRunProgressObserver capturingObserver =
|
||||||
|
new de.gecheckt.pdf.umbenenner.application.port.in.BatchRunProgressObserver() {
|
||||||
|
@Override
|
||||||
|
public void onRunStarted(RunId runId, int totalCandidates) {
|
||||||
|
// No GUI feedback for scheduler-driven runs.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDocumentCompleted(
|
||||||
|
de.gecheckt.pdf.umbenenner.application.port.in.DocumentCompletionEvent event) {
|
||||||
|
// No per-document feedback for scheduler-driven runs.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRunEnded(
|
||||||
|
de.gecheckt.pdf.umbenenner.application.port.in.RunSummary summary) {
|
||||||
|
capturedSummary.set(summary);
|
||||||
|
}
|
||||||
|
};
|
||||||
BatchRunProcessingUseCase useCase = buildProductionBatchUseCase(
|
BatchRunProcessingUseCase useCase = buildProductionBatchUseCase(
|
||||||
ctx.startConfiguration(), runLockPort,
|
ctx.startConfiguration(), runLockPort,
|
||||||
de.gecheckt.pdf.umbenenner.application.port.in.BatchRunProgressObserver.noOp(),
|
capturingObserver,
|
||||||
de.gecheckt.pdf.umbenenner.application.port.in.BatchRunCancellationToken.neverCancelled());
|
de.gecheckt.pdf.umbenenner.application.port.in.BatchRunCancellationToken.neverCancelled());
|
||||||
BatchRunOutcome outcome = useCase.execute(runContext);
|
BatchRunOutcome outcome = useCase.execute(runContext);
|
||||||
runContext.setEndInstant(Instant.now());
|
runContext.setEndInstant(Instant.now());
|
||||||
if (outcome.isLockUnavailable()) {
|
if (outcome.isLockUnavailable()) {
|
||||||
return new BatchRunTriggerResult.SkippedBusy();
|
return new BatchRunTriggerResult.SkippedBusy();
|
||||||
} else if (outcome.isSuccess()) {
|
} else if (outcome.isSuccess()) {
|
||||||
return new BatchRunTriggerResult.Started(Instant.now(), RunSummary.noOp());
|
de.gecheckt.pdf.umbenenner.application.port.in.RunSummary inSummary =
|
||||||
|
capturedSummary.get();
|
||||||
|
RunSummary outSummary = inSummary == null
|
||||||
|
? RunSummary.noOp()
|
||||||
|
: new RunSummary(
|
||||||
|
inSummary.successCount(),
|
||||||
|
inSummary.failedCount(),
|
||||||
|
inSummary.skippedCount());
|
||||||
|
return new BatchRunTriggerResult.Started(Instant.now(), outSummary);
|
||||||
} else {
|
} else {
|
||||||
return new BatchRunTriggerResult.Failed(
|
return new BatchRunTriggerResult.Failed(
|
||||||
"Verarbeitungslauf fehlgeschlagen.",
|
"Verarbeitungslauf fehlgeschlagen.",
|
||||||
|
|||||||
Reference in New Issue
Block a user