package events
import (
"testing"
"time"
)
func TestAttendSequencesPerAccount(t *testing.T) {
ev, err := Start(t.TempDir())
if err != nil {
t.Fatal(err)
}
defer ev.Close()
var seqs []uint64
for i := 0; i < 3; i++ {
seq, err := ev.Attend("alice", AttentionEvent{Kind: "mention", Repo: "alpha", From: "bob"})
if err != nil {
t.Fatal(err)
}
seqs = append(seqs, seq)
}
for i := 1; i < len(seqs); i++ {
if seqs[i] <= seqs[i-1] {
t.Fatalf("sequence went backwards: %v", seqs)
}
}
}
// Two accounts share one stream, so every per-account number must come
// from a subject-filtered view — the exact bug chat.Unread documents.
func TestAttentionAccountsAreIsolated(t *testing.T) {
ev, err := Start(t.TempDir())
if err != nil {
t.Fatal(err)
}
defer ev.Close()
for i := 0; i < 3; i++ {
if _, err := ev.Attend("alice", AttentionEvent{Kind: "mention"}); err != nil {
t.Fatal(err)
}
}
if _, err := ev.Attend("bob", AttentionEvent{Kind: "mention"}); err != nil {
t.Fatal(err)
}
a, err := ev.AttentionPending("alice", 0)
if err != nil {
t.Fatal(err)
}
b, err := ev.AttentionPending("bob", 0)
if err != nil {
t.Fatal(err)
}
if a != 3 || b != 1 {
t.Fatalf("pending alice=%d bob=%d, want 3 and 1", a, b)
}
recent, err := ev.RecentAttention("bob", 10)
if err != nil {
t.Fatal(err)
}
if len(recent) != 1 {
t.Fatalf("bob has %d recent events, want 1", len(recent))
}
}
func TestTailAttentionFromSeq(t *testing.T) {
ev, err := Start(t.TempDir())
if err != nil {
t.Fatal(err)
}
defer ev.Close()
var seqs []uint64
for _, s := range []string{"one", "two", "three"} {
seq, err := ev.Attend("alice", AttentionEvent{Kind: "mention", Summary: s})
if err != nil {
t.Fatal(err)
}
seqs = append(seqs, seq)
}
ch, cancel := ev.TailAttention("alice", seqs[0])
defer cancel()
var got []string
for i := 0; i < 2; i++ {
select {
case e, open := <-ch:
if !open {
t.Fatal("tail closed early")
}
got = append(got, e.Summary)
if e.Seq != seqs[i+1] {
t.Fatalf("event %d carries seq %d, want %d", i, e.Seq, seqs[i+1])
}
case <-time.After(5 * time.Second):
t.Fatal("tail delivered nothing")
}
}
if got[0] != "two" || got[1] != "three" {
t.Fatalf("tail from %d replayed %v, want [two three]", seqs[0], got)
}
}
// Pending counts from a read pointer, which is what the local inbox
// count is: "events after the pointer", never last-minus-since.
func TestAttentionPendingSince(t *testing.T) {
ev, err := Start(t.TempDir())
if err != nil {
t.Fatal(err)
}
defer ev.Close()
var last uint64
for i := 0; i < 4; i++ {
seq, err := ev.Attend("alice", AttentionEvent{Kind: "mention"})
if err != nil {
t.Fatal(err)
}
if i == 1 {
last = seq
}
}
n, err := ev.AttentionPending("alice", last)
if err != nil {
t.Fatal(err)
}
if n != 2 {
t.Fatalf("pending after %d = %d, want 2", last, n)
}
}
func TestAttendRejectsUnroutableAccount(t *testing.T) {
ev, err := Start(t.TempDir())
if err != nil {
t.Fatal(err)
}
defer ev.Close()
for _, bad := range []string{"", "a.b", "*", ">", "with space"} {
if _, err := ev.Attend(bad, AttentionEvent{Kind: "mention"}); err == nil {
t.Fatalf("Attend accepted account %q", bad)
}
}
}
// A resume point older than the window is a resume that looks
// successful and is not, so the tail says so before replaying.
func TestTailAttentionSignalsGap(t *testing.T) {
ev, err := Start(t.TempDir())
if err != nil {
t.Fatal(err)
}
defer ev.Close()
// Somebody else's events come first, so alice's own first sequence
// is well above the resume point asked for below.
for i := 0; i < 3; i++ {
if _, err := ev.Attend("bob", AttentionEvent{Kind: "mention"}); err != nil {
t.Fatal(err)
}
}
seq, err := ev.Attend("alice", AttentionEvent{Kind: "mention"})
if err != nil {
t.Fatal(err)
}
// Ask to resume from well before anything this subject holds.
ch, cancel := ev.TailAttention("alice", 1)
defer cancel()
select {
case e := <-ch:
if e.Kind != KindAttnGap {
t.Fatalf("first delivery is %q, want %q", e.Kind, KindAttnGap)
}
if e.Seq != seq || e.Ref != 1 {
t.Fatalf("gap carries seq=%d ref=%d, want %d and 1", e.Seq, e.Ref, seq)
}
case <-time.After(5 * time.Second):
t.Fatal("no gap signal")
}
}