32687b3712
The gate honours State.SnoozeUntil but nothing ever filled it. New store.SnoozedUntil returns, per rule, when the newest snooze runs out. Reviewer: the fixed 2h SnoozeDuration and its reasoning in nudges.go — nothing upstream can supply a per-nudge length, so no new column. Expired snoozes are dropped in SQL, so silence can never be permanent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CGeSZxh1DCtRxmFVSYVGvJ
105 lines
2.9 KiB
Go
105 lines
2.9 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// snoozeNudge records a nudge and immediately snoozes it at ts.
|
|
func snoozeNudge(t *testing.T, s *Store, rule string, ts time.Time) {
|
|
t.Helper()
|
|
ctx := context.Background()
|
|
id, err := s.RecordNudge(ctx, rule, "voice", "drink water", ts)
|
|
if err != nil {
|
|
t.Fatalf("RecordNudge: %v", err)
|
|
}
|
|
if err := s.ResolveNudge(ctx, id, NudgeSnoozed, ts); err != nil {
|
|
t.Fatalf("ResolveNudge: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSnoozedUntilPerRule(t *testing.T) {
|
|
s := newTestStore(t)
|
|
now := time.Now().UTC().Truncate(time.Millisecond)
|
|
|
|
snoozeNudge(t, s, "water", now.Add(-10*time.Minute))
|
|
snoozeNudge(t, s, "break", now.Add(-30*time.Minute))
|
|
|
|
got, err := s.SnoozedUntil(context.Background(), now)
|
|
if err != nil {
|
|
t.Fatalf("SnoozedUntil: %v", err)
|
|
}
|
|
if len(got) != 2 {
|
|
t.Fatalf("want 2 snoozed rules, got %v", got)
|
|
}
|
|
wantWater := now.Add(-10 * time.Minute).Add(SnoozeDuration)
|
|
if !got["water"].Equal(wantWater) {
|
|
t.Fatalf("water until = %v, want %v", got["water"], wantWater)
|
|
}
|
|
}
|
|
|
|
// The map must only ever hold the newest snooze for a rule, so a stale one
|
|
// can't shorten (or lengthen) the live one.
|
|
func TestSnoozedUntilUsesNewestSnooze(t *testing.T) {
|
|
s := newTestStore(t)
|
|
now := time.Now().UTC().Truncate(time.Millisecond)
|
|
|
|
snoozeNudge(t, s, "water", now.Add(-90*time.Minute))
|
|
snoozeNudge(t, s, "water", now.Add(-5*time.Minute))
|
|
|
|
got, err := s.SnoozedUntil(context.Background(), now)
|
|
if err != nil {
|
|
t.Fatalf("SnoozedUntil: %v", err)
|
|
}
|
|
want := now.Add(-5 * time.Minute).Add(SnoozeDuration)
|
|
if !got["water"].Equal(want) {
|
|
t.Fatalf("water until = %v, want %v", got["water"], want)
|
|
}
|
|
}
|
|
|
|
// A snooze must expire. If this ever regresses Maven goes quiet forever and
|
|
// nobody can tell why.
|
|
func TestSnoozedUntilExpires(t *testing.T) {
|
|
s := newTestStore(t)
|
|
now := time.Now().UTC().Truncate(time.Millisecond)
|
|
|
|
snoozeNudge(t, s, "water", now.Add(-SnoozeDuration-time.Minute))
|
|
|
|
got, err := s.SnoozedUntil(context.Background(), now)
|
|
if err != nil {
|
|
t.Fatalf("SnoozedUntil: %v", err)
|
|
}
|
|
if _, ok := got["water"]; ok {
|
|
t.Fatalf("expired snooze still active: %v", got)
|
|
}
|
|
}
|
|
|
|
// Other outcomes are not snoozes.
|
|
func TestSnoozedUntilIgnoresOtherOutcomes(t *testing.T) {
|
|
s := newTestStore(t)
|
|
ctx := context.Background()
|
|
now := time.Now().UTC().Truncate(time.Millisecond)
|
|
|
|
for _, outcome := range []string{NudgeActed, NudgeIgnored} {
|
|
id, err := s.RecordNudge(ctx, "water", "voice", "drink water", now)
|
|
if err != nil {
|
|
t.Fatalf("RecordNudge: %v", err)
|
|
}
|
|
if err := s.ResolveNudge(ctx, id, outcome, now); err != nil {
|
|
t.Fatalf("ResolveNudge: %v", err)
|
|
}
|
|
}
|
|
if _, err := s.RecordNudge(ctx, "break", "voice", "stand up", now); err != nil {
|
|
t.Fatalf("RecordNudge: %v", err)
|
|
}
|
|
|
|
got, err := s.SnoozedUntil(ctx, now)
|
|
if err != nil {
|
|
t.Fatalf("SnoozedUntil: %v", err)
|
|
}
|
|
if len(got) != 0 {
|
|
t.Fatalf("want no snoozes, got %v", got)
|
|
}
|
|
}
|