From a6b8a2b744de0f30e3a2df89ba47c3b5c91df872 Mon Sep 17 00:00:00 2001 From: Mathias Date: Fri, 2 Oct 2026 08:53:33 +0200 Subject: [PATCH] test(progress): a keyed soft failure that logs why, a note that costs no write, and FailRunning for the job's own failure (red) Co-Authored-By: Claude Sonnet 5.5 --- internal/progress/tracker_test.go | 36 +++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/internal/progress/tracker_test.go b/internal/progress/tracker_test.go index f7e4928..cf78319 100644 --- a/internal/progress/tracker_test.go +++ b/internal/progress/tracker_test.go @@ -182,14 +182,14 @@ func TestTracker_FinishSkipsWhatNeverRanAndClosesWhatDid(t *testing.T) { require.Equal(t, domain.StepSkipped, stateOf(steps, "save"), "a step that never ran is skipped, not left pending") } -func TestTracker_FailMarksTheRunningStepFailedAndLeavesTheRestPending(t *testing.T) { +func TestTracker_FailRunningMarksTheRunningStepFailedAndLeavesTheRestPending(t *testing.T) { tr, sink, _, _ := newTracker(t) tr.Start() tr.Begin("audio", 0) tr.Done("audio") tr.Begin("decide", 5) - tr.Fail() + tr.FailRunning() steps := sink.last(t) require.Equal(t, domain.StepDone, stateOf(steps, "audio")) @@ -197,6 +197,35 @@ func TestTracker_FailMarksTheRunningStepFailedAndLeavesTheRestPending(t *testing require.Equal(t, domain.StepPending, stateOf(steps, "save"), "what never started stays pending: it shows where the job stopped") } +func TestTracker_AKeyedFailMarksOneStepFailedLogsWhyAndTheJobGoesOn(t *testing.T) { + tr, sink, _, buf := newTracker(t) + tr.Start() + tr.Begin("audio", 0) + + tr.Fail("audio", errors.New("gateway said no")) + tr.Begin("decide", 5) + + steps := sink.last(t) + require.Equal(t, domain.StepFailed, stateOf(steps, "audio")) + require.Equal(t, domain.StepRunning, stateOf(steps, "decide"), "a soft failure must not stop the next step from running") + require.Contains(t, buf.String(), "level=WARN") + require.Contains(t, buf.String(), "gateway said no") + require.Contains(t, buf.String(), "step=audio") +} + +func TestTracker_NoteIsALogLineAndNeverASave(t *testing.T) { + tr, sink, _, buf := newTracker(t) + tr.Start() + before := sink.saveCount() + + tr.Note("reconciled", "disputes", 362, "auto_resolved", 80) + + require.Equal(t, before, sink.saveCount(), "a note is for the operator; it must not cost a database write") + require.Contains(t, buf.String(), "reconciled") + require.Contains(t, buf.String(), "disputes=362") + require.Contains(t, buf.String(), "auto_resolved=80") +} + func TestTracker_AnUnknownKeyIsIgnored(t *testing.T) { tr, sink, _, _ := newTracker(t) tr.Start() @@ -206,6 +235,7 @@ func TestTracker_AnUnknownKeyIsIgnored(t *testing.T) { tr.Begin("no-such-step", 0) tr.Advance("no-such-step", 1) tr.Done("no-such-step") + tr.Fail("no-such-step", errors.New("x")) }) require.Equal(t, before, sink.saveCount()) } @@ -298,6 +328,8 @@ func TestFrom_WithoutAReporterReturnsOneThatDoesNothing(t *testing.T) { r.Begin("x", 3) r.Advance("x", 1) r.Done("x") + r.Fail("x", errors.New("e")) + r.Note("n", "k", 1) }) }