76d123edf3
LastSent scanned MAX(sent_at) over an empty ack_sends into a bare int64, so the ordinary "nothing sent yet" case came back as a scan error rather than the zero time its doc promises. ack_sends is written only by MarkSent, and MarkSent runs only after a repeat has gone out, so every rule's FIRST repeat read an empty table — and RepeatUnacked aborts its whole sweep on that error. The repeat-til-ack loop could never take its first step. Scans into a NullInt64, the same way OldestPendingTelegram already does two files over. EnqueueDigestEntry deduped against any row still marked pending, including one already past its expires_ts. The tick enqueues before it sweeps, so a suppressed nudge arriving on the tick after an expiry was told deduped=true against an entry PendingDigestEntries will never hand back: the caller drops the phrasing it just paid the LLM for and nothing reaches the bundle. The dedupe now carries the same expiry test the read side does. Its lookup also stops treating a real read failure as "nothing there".
61 lines
1.9 KiB
Go
61 lines
1.9 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestLastSentOnEmptyTableIsZero pins the aggregate-over-nothing trap that
|
|
// nudges.go's OldestPendingTelegram already documents: MAX over an empty set is
|
|
// one row holding NULL, not zero rows. Scanning that into a bare int64 is an
|
|
// error, and the doc on LastSent promises a zero time instead.
|
|
//
|
|
// It is not a cosmetic promise. ack_sends is written only by MarkSent, and
|
|
// MarkSent is called only after a repeat has already gone out, so the first
|
|
// repeat for every rule reads an empty table. delivery.Dispatcher.RepeatUnacked
|
|
// aborts the whole sweep on that error, which means the repeat-til-ack loop can
|
|
// never take its first step for any rule.
|
|
func TestLastSentOnEmptyTableIsZero(t *testing.T) {
|
|
s := newTestStore(t)
|
|
ctx := context.Background()
|
|
|
|
last, err := s.LastSent(ctx, "service_down")
|
|
if err != nil {
|
|
t.Fatalf("last sent on an empty table must not error: %v", err)
|
|
}
|
|
if !last.IsZero() {
|
|
t.Fatalf("want the zero time before anything was sent, got %v", last)
|
|
}
|
|
}
|
|
|
|
// TestLastSentIsScopedToItsRule — a send for another rule must not answer for
|
|
// this one, or the repeat interval is clocked off somebody else's alarm.
|
|
func TestLastSentIsScopedToItsRule(t *testing.T) {
|
|
s := newTestStore(t)
|
|
ctx := context.Background()
|
|
at := time.UnixMilli(1_700_000_000_000).UTC()
|
|
|
|
if err := s.MarkSent(ctx, "other_rule", at); err != nil {
|
|
t.Fatalf("mark sent: %v", err)
|
|
}
|
|
last, err := s.LastSent(ctx, "service_down")
|
|
if err != nil {
|
|
t.Fatalf("last sent: %v", err)
|
|
}
|
|
if !last.IsZero() {
|
|
t.Fatalf("want zero for a rule with no sends, got %v", last)
|
|
}
|
|
|
|
if err := s.MarkSent(ctx, "service_down", at); err != nil {
|
|
t.Fatalf("mark sent: %v", err)
|
|
}
|
|
last, err = s.LastSent(ctx, "service_down")
|
|
if err != nil {
|
|
t.Fatalf("last sent: %v", err)
|
|
}
|
|
if !last.Equal(at) {
|
|
t.Fatalf("want %v, got %v", at, last)
|
|
}
|
|
}
|