package main import ( "context" "testing" "time" "github.com/novox/mesh-controller/internal/conditions" "github.com/novox/mesh-controller/internal/link" ) // **A dead letter that ages out clears its condition and says so** (novox/hq ADR 0376 ยง6), on a fake clock: while // DEAD_LETTERS holds a restricted stream's messages for a consumer its max-deliveries condition is open; once they // have aged out it clears, with words saying how many aged out undelivered and from which stream. One dropped before // its age is not counted as aged out, and a consumer of EVENTS clears as before. func TestADeadLetterThatAgesOutClearsItsConditionAndSaysSo(t *testing.T) { now := time.Date(2026, 10, 15, 12, 0, 0, 0, time.UTC) store := conditions.NewInMemory() told := &conditions.Told{} k := conditions.NewKeeper(t.Context(), conditions.Options{Store: store, History: store, Teller: told, Now: func() time.Time { return now }}) w := &watchdogs{keeper: k, started: now.Add(-time.Hour)} look := func(at time.Time, held map[string]int, aging map[string][]link.Aging) { f := calm(at) f.deadLetters, f.deadLettersAging = held, aging w.see(t.Context(), f) } mail := "MAIL.ace_mailer" look(now, map[string]int{mail: 3, "EVENTS.media_sonarr": 1}, map[string][]link.Aging{mail: { {ID: 1, AgesOut: now.Add(time.Hour)}, {ID: 2, AgesOut: now.Add(2 * time.Hour)}, {ID: 3, AgesOut: now.Add(3 * time.Hour)}}}) if open, _ := k.Open(t.Context()); len(open) != 2 { t.Fatalf("open: %+v", open) } // One dropped by a person before its age: not aged out. look(now.Add(30*time.Minute), map[string]int{mail: 2, "EVENTS.media_sonarr": 1}, map[string][]link.Aging{mail: { {ID: 2, AgesOut: now.Add(2 * time.Hour)}, {ID: 3, AgesOut: now.Add(3 * time.Hour)}}}) look(now.Add(150*time.Minute), map[string]int{mail: 1}, map[string][]link.Aging{mail: {{ID: 3, AgesOut: now.Add(3 * time.Hour)}}}) open, _ := k.Open(t.Context()) if len(open) != 1 || open[0].Key != conditions.Key(conditions.ScopeBus, mail, link.AdvisoryMaxDeliveries) { t.Fatalf("after one aged out: %+v", open) } look(now.Add(4*time.Hour), map[string]int{}, map[string][]link.Aging{}) if open, _ := k.Open(t.Context()); len(open) != 0 { t.Fatalf("after all aged out: %+v", open) } k.Close(context.Background()) // everything said is said once it closes var mailCleared, sonarrCleared string for _, e := range told.Said() { if e.Change != conditions.ChangeCleared { continue } switch e.Condition.Key { case conditions.Key(conditions.ScopeBus, mail, link.AdvisoryMaxDeliveries): mailCleared = e.Condition.Resolved case conditions.Key(conditions.ScopeBus, "EVENTS.media_sonarr", link.AdvisoryMaxDeliveries): sonarrCleared = e.Condition.Resolved } } if mailCleared != "Resolved: 2 messages of the stream MAIL aged out undelivered" { t.Fatalf("MAIL's condition cleared with %q", mailCleared) } if sonarrCleared != "Resolved: the messages it gave up on were delivered again or dropped" { t.Fatalf("an EVENTS consumer's condition cleared with %q", sonarrCleared) } if why, ok := conditions.Plain(mailCleared); !ok { t.Fatalf("not plain: %s", why) } }