Artifact fe650e0b402dfffef5358b52defc8931b83d481d4a2d8fbf7680871abe690b3e:
- File
internal/ingest/stats_test.go
— part of check-in
[5c9f9f41b1]
at
2026-08-17 17:50:04
on branch trunk
— fleet: per-store ingest health and manual sync
Adds fleet.status and fleet.sync ops (admin) and a /fleet page: last round, artifacts, parse fallbacks, and consecutive failures per store. With eleven repos a wedged store is invisible on a page that shows only counts — its counts are whatever they were when it broke.
gate gains gateAt so the fleet pages can ask for Admin at an empty repo rather than Token at a repo; forbid now words the refusal to match. (user: dmestas, size: 2888) [annotate] [blame] [check-ins using]
package ingest import ( "errors" "testing" "time" ) // The whole reason the fleet admin surface exists is that a wedged store // is invisible: its counts are exactly the counts it had when it broke, // so a page showing only counts cannot tell it from a healthy one. That // makes the failure bookkeeping — not the success bookkeeping — the part // worth pinning down. func TestStatsDistinguishWedgedFromFlapping(t *testing.T) { p := &Pipeline{} if got := p.Stats(); got.Failures != 0 || !got.LastOK.IsZero() { t.Fatalf("fresh pipeline already has history: %+v", got) } // A store that has never succeeded must not read as healthy just // because nothing has run yet: LastOK stays zero. p.record(0, time.Millisecond, errors.New("boom")) p.record(0, time.Millisecond, errors.New("boom")) p.record(0, time.Millisecond, errors.New("boom")) s := p.Stats() if s.Failures != 3 { t.Errorf("Failures = %d, want 3 consecutive", s.Failures) } if !s.LastOK.IsZero() { t.Errorf("LastOK set despite never succeeding: %v", s.LastOK) } if s.LastErr != "boom" { t.Errorf("LastErr = %q, want the failure preserved", s.LastErr) } if s.LastErrAt.IsZero() { t.Error("LastErrAt not stamped") } // One success must clear the streak — otherwise a store that // recovered still reads as broken forever, and an operator learns to // ignore the column. p.record(7, 2*time.Millisecond, nil) s = p.Stats() if s.Failures != 0 { t.Errorf("Failures = %d after recovery, want 0", s.Failures) } if s.LastErr != "" { t.Errorf("LastErr = %q after recovery, want cleared", s.LastErr) } if s.LastOK.IsZero() { t.Error("LastOK not stamped on success") } if s.Total != 7 || s.LastCount != 7 { t.Errorf("Total/LastCount = %d/%d, want 7/7", s.Total, s.LastCount) } // A later failure must NOT erase the fact that the store was once // healthy, or "failing since" becomes unanswerable. p.record(0, time.Millisecond, errors.New("again")) s = p.Stats() if s.LastOK.IsZero() { t.Error("LastOK cleared by a subsequent failure") } if s.Failures != 1 { t.Errorf("Failures = %d, want the streak restarted at 1", s.Failures) } // Totals are cumulative across the run, so a failed round must not // discard what earlier rounds published. if s.Total != 7 { t.Errorf("Total = %d after a failed round, want 7 retained", s.Total) } } // Fallbacks accumulate independently of round outcome: an artifact that // would not parse is not a round failure, and conflating the two would // make every fossil-born corpus look broken. func TestFallbacksAreNotFailures(t *testing.T) { p := &Pipeline{} p.fallback() p.fallback() p.record(2, time.Millisecond, nil) s := p.Stats() if s.Fallbacks != 2 { t.Errorf("Fallbacks = %d, want 2", s.Fallbacks) } if s.Failures != 0 || s.LastErr != "" { t.Errorf("parse fallbacks reported as a round failure: %+v", s) } }