package store import ( "context" "testing" "time" ) // TestDroppedDeliveryAttemptRoundTrips — Vikunja #370. A suppressed nudge is // recorded as 'dropped'. The status column has a CHECK constraint, so this // only works if migration #12 widened it; a fake outbox in a unit test would // not catch that. func TestDroppedDeliveryAttemptRoundTrips(t *testing.T) { s := newTestStore(t) ctx := context.Background() now := time.Now() id, err := s.BeginDeliveryAttempt(ctx, "nudge", "water", 0, "", "drop", "abc123", now) if err != nil { t.Fatalf("BeginDeliveryAttempt: %v", err) } if err := s.CompleteDeliveryAttempt(ctx, id, DeliveryDropped, now); err != nil { t.Fatalf("CompleteDeliveryAttempt: %v", err) } var status string err = s.db.QueryRowContext(ctx, `SELECT status FROM delivery_attempts WHERE id = ?`, id).Scan(&status) if err != nil { t.Fatalf("read back: %v", err) } if status != DeliveryDropped { t.Fatalf("status: want %q, got %q", DeliveryDropped, status) } } // TestListDeliveryAttempts — the read path the outbox lacked until #390. The // two questions it must answer are "what was dropped" and "what is pending". func TestListDeliveryAttempts(t *testing.T) { ctx := context.Background() s := newTestStore(t) base := time.Date(2026, 8, 4, 9, 0, 0, 0, time.UTC) sent, err := s.BeginDeliveryAttempt(ctx, "nudge", "water", 0, "", "telegram", "h1", base) if err != nil { t.Fatal(err) } if err := s.CompleteDeliveryAttempt(ctx, sent, DeliverySent, base.Add(time.Second)); err != nil { t.Fatal(err) } dropped, err := s.BeginDeliveryAttempt(ctx, "nudge", "care", 0, "", "telegram", "h2", base.Add(time.Minute)) if err != nil { t.Fatal(err) } if err := s.CompleteDeliveryAttempt(ctx, dropped, DeliveryDropped, base.Add(time.Minute)); err != nil { t.Fatal(err) } if _, err := s.BeginDeliveryAttempt(ctx, "reminder", "", 7, "reminder:test", "voice", "h3", base.Add(2*time.Minute)); err != nil { t.Fatal(err) } all, err := s.ListDeliveryAttempts(ctx, "", 10) if err != nil || len(all) != 3 { t.Fatalf("ListDeliveryAttempts = %d rows, err=%v, want 3", len(all), err) } // Newest first. if all[0].Kind != "reminder" || all[0].ReminderID != 7 { t.Fatalf("newest row is %+v, want the reminder", all[0]) } if all[0].HasComplete { t.Fatalf("a pending row must have no completion time: %+v", all[0]) } if !all[2].HasComplete || !all[2].Completed.Equal(base.Add(time.Second)) { t.Fatalf("completed row lost its time: %+v", all[2]) } only, err := s.ListDeliveryAttempts(ctx, DeliveryDropped, 10) if err != nil || len(only) != 1 || only[0].Rule != "care" { t.Fatalf("dropped filter = %+v, err=%v", only, err) } pending, err := s.ListDeliveryAttempts(ctx, DeliveryPending, 10) if err != nil || len(pending) != 1 || pending[0].Kind != "reminder" { t.Fatalf("pending filter = %+v, err=%v", pending, err) } }