eventlog_test.go
1package eventlog
2
3import (
4 "context"
5 "net/http/httptest"
6 "path/filepath"
7 "strings"
8 "sync"
9 "testing"
10 "time"
11
12 "git.kilimanjaro.io/ci/internal/lex"
13)
14
15func open(t *testing.T) *Log {
16 t.Helper()
17 l, err := Open(filepath.Join(t.TempDir(), "events.db"))
18 if err != nil {
19 t.Fatal(err)
20 }
21 t.Cleanup(func() { l.Close() })
22 return l
23}
24
25func TestAppendListMonotonic(t *testing.T) {
26 l := open(t)
27 var prev int64
28 for i := range 100 {
29 ev, err := l.Append("x.test", "", map[string]int{"i": i})
30 if err != nil {
31 t.Fatal(err)
32 }
33 if ev.Created <= prev {
34 t.Fatalf("created not increasing: %d after %d", ev.Created, prev)
35 }
36 prev = ev.Created
37 }
38 all, _ := l.List(0, 1000)
39 if len(all) != 100 {
40 t.Fatalf("%d events", len(all))
41 }
42 tail, _ := l.List(all[89].Created, 1000)
43 if len(tail) != 10 || string(tail[0].Event) != `{"i":90}` {
44 t.Errorf("after cursor: %d %s", len(tail), tail[0].Event)
45 }
46 // reopening keeps the clock ahead of what's stored
47 path := filepath.Join(t.TempDir(), "e.db")
48 a, _ := Open(path)
49 e1, _ := a.Append("x", "", 1)
50 a.Close()
51 b, _ := Open(path)
52 defer b.Close()
53 if b.last != e1.Created {
54 t.Errorf("reopened last %d want %d", b.last, e1.Created)
55 }
56}
57
58func TestStreamReplayTailAndResume(t *testing.T) {
59 l := open(t)
60 for i := range 3 {
61 l.Append("x.test", "", i)
62 }
63 srv := httptest.NewServer(RequireToken("s3cret", l))
64 defer srv.Close()
65 url := "ws" + strings.TrimPrefix(srv.URL, "http")
66
67 var mu sync.Mutex
68 var got []lex.Event
69 ctx, cancel := context.WithCancel(context.Background())
70 done := make(chan error)
71 go func() {
72 done <- Subscribe(ctx, url, 0, "s3cret", func(ev lex.Event) error {
73 mu.Lock()
74 got = append(got, ev)
75 mu.Unlock()
76 return nil
77 })
78 }()
79 wait := func(n int) {
80 t.Helper()
81 for range 200 {
82 mu.Lock()
83 k := len(got)
84 mu.Unlock()
85 if k >= n {
86 return
87 }
88 time.Sleep(10 * time.Millisecond)
89 }
90 t.Fatalf("got %d events, want %d", len(got), n)
91 }
92 wait(3)
93 l.Append("x.test", "", 3) // tail
94 wait(4)
95 cancel()
96 <-done
97
98 // resume from the last cursor: only newer events
99 l.Append("x.test", "", 4)
100 var resumed []lex.Event
101 ctx2, cancel2 := context.WithCancel(context.Background())
102 go func() {
103 _ = Subscribe(ctx2, url, got[3].Created, "s3cret", func(ev lex.Event) error {
104 resumed = append(resumed, ev)
105 cancel2()
106 return nil
107 })
108 }()
109 <-ctx2.Done()
110 time.Sleep(20 * time.Millisecond)
111 if len(resumed) != 1 || string(resumed[0].Event) != "4" {
112 t.Errorf("resumed %+v", resumed)
113 }
114}
115
116func TestStreamNeedsToken(t *testing.T) {
117 l := open(t)
118 srv := httptest.NewServer(RequireToken("s3cret", l))
119 defer srv.Close()
120 url := "ws" + strings.TrimPrefix(srv.URL, "http")
121 var c int64
122 if err := follow(context.Background(), url, &c, "wrong", func(lex.Event) error { return nil }); err == nil || !strings.Contains(err.Error(), "401") {
123 t.Errorf("wrong token: %v", err)
124 }
125}