pipeline_test.mx raw
1 package pipeline
2
3 import (
4 "testing"
5 "time"
6
7 "smesh.lol/pkg/acl"
8 "smesh.lol/pkg/nostr/event"
9 "smesh.lol/pkg/nostr/kind"
10 "smesh.lol/pkg/nostr/signer/p8k"
11 "smesh.lol/pkg/nostr/tag"
12 "smesh.lol/pkg/relay/ratelimit"
13 "smesh.lol/pkg/store"
14 )
15
16 func setup(t *testing.T) (*Pipeline, *p8k.Signer) {
17 t.Helper()
18 eng, err := store.Open(t.TempDir())
19 if err != nil {
20 t.Fatal(err)
21 }
22 t.Cleanup(func() { eng.Close() })
23
24 s := p8k.MustNew()
25 if err := s.Generate(); err != nil {
26 t.Fatal(err)
27 }
28 p := New(eng, acl.Open{}, nil, DefaultConfig())
29 return p, s
30 }
31
32 func makeEv(t *testing.T, s *p8k.Signer, k uint16, ts int64, tags *tag.S, content string) *event.E {
33 t.Helper()
34 ev := &event.E{
35 CreatedAt: ts,
36 Kind: k,
37 Tags: tags,
38 Content: []byte(content),
39 }
40 if err := ev.Sign(s); err != nil {
41 t.Fatal(err)
42 }
43 return ev
44 }
45
46 func TestIngestRegular(t *testing.T) {
47 p, s := setup(t)
48 ev := makeEv(t, s, 1, time.Now().Unix(), nil, "hello")
49 r := p.Ingest(ev)
50 if !r.OK {
51 t.Fatalf("expected OK, got: %s", r.Reason)
52 }
53 }
54
55 func TestIngestDuplicate(t *testing.T) {
56 p, s := setup(t)
57 ev := makeEv(t, s, 1, time.Now().Unix(), nil, "hello")
58 r := p.Ingest(ev)
59 if !r.OK {
60 t.Fatalf("first ingest failed: %s", r.Reason)
61 }
62 r = p.Ingest(ev)
63 if r.OK {
64 t.Fatal("expected duplicate rejection")
65 }
66 if string(r.Reason) != "duplicate: already have this event" {
67 t.Fatalf("unexpected reason: %s", r.Reason)
68 }
69 }
70
71 func TestIngestBadSignature(t *testing.T) {
72 p, s := setup(t)
73 ev := makeEv(t, s, 1, time.Now().Unix(), nil, "hello")
74 // Corrupt signature.
75 ev.Sig[0] ^= 0xFF
76 r := p.Ingest(ev)
77 if r.OK {
78 t.Fatal("expected rejection for bad sig")
79 }
80 }
81
82 func TestIngestACLBlocked(t *testing.T) {
83 eng, err := store.Open(t.TempDir())
84 if err != nil {
85 t.Fatal(err)
86 }
87 defer eng.Close()
88
89 s := p8k.MustNew()
90 if err := s.Generate(); err != nil {
91 t.Fatal(err)
92 }
93
94 // Whitelist with a different pubkey.
95 other := p8k.MustNew()
96 if err := other.Generate(); err != nil {
97 t.Fatal(err)
98 }
99 wl := &acl.Whitelist{Pubkeys: [][]byte{other.Pub()}}
100 p := New(eng, wl, nil, DefaultConfig())
101
102 ev := makeEv(t, s, 1, time.Now().Unix(), nil, "blocked")
103 r := p.Ingest(ev)
104 if r.OK {
105 t.Fatal("expected ACL rejection")
106 }
107 if string(r.Reason) != "blocked: pubkey not allowed" {
108 t.Fatalf("unexpected reason: %s", r.Reason)
109 }
110 }
111
112 func TestIngestRateLimit(t *testing.T) {
113 eng, err := store.Open(t.TempDir())
114 if err != nil {
115 t.Fatal(err)
116 }
117 defer eng.Close()
118
119 s := p8k.MustNew()
120 if err := s.Generate(); err != nil {
121 t.Fatal(err)
122 }
123
124 // 1 token/sec, burst 2.
125 lim := ratelimit.New(1.0, 2)
126 p := New(eng, acl.Open{}, lim, DefaultConfig())
127
128 now := time.Now().Unix()
129 for i := 0; i < 2; i++ {
130 ev := makeEv(t, s, 1, now+int64(i), nil, "msg")
131 r := p.Ingest(ev)
132 if !r.OK {
133 t.Fatalf("event %d should be allowed: %s", i, r.Reason)
134 }
135 }
136 // Third event should be rate-limited.
137 ev := makeEv(t, s, 1, now+2, nil, "msg")
138 r := p.Ingest(ev)
139 if r.OK {
140 t.Fatal("expected rate limit rejection")
141 }
142 }
143
144 func TestIngestEphemeral(t *testing.T) {
145 p, s := setup(t)
146 // Kind 20001 is ephemeral.
147 ev := makeEv(t, s, 20001, time.Now().Unix(), nil, "ephemeral")
148 r := p.Ingest(ev)
149 if !r.OK {
150 t.Fatalf("ephemeral should be accepted: %s", r.Reason)
151 }
152 }
153
154 func TestIngestReplaceable(t *testing.T) {
155 p, s := setup(t)
156 now := time.Now().Unix()
157
158 // Store initial kind-0 (metadata).
159 ev1 := makeEv(t, s, 0, now, nil, `{"name":"old"}`)
160 r := p.Ingest(ev1)
161 if !r.OK {
162 t.Fatalf("first replaceable failed: %s", r.Reason)
163 }
164
165 // Newer replaces it.
166 ev2 := makeEv(t, s, 0, now+1, nil, `{"name":"new"}`)
167 r = p.Ingest(ev2)
168 if !r.OK {
169 t.Fatalf("newer replaceable failed: %s", r.Reason)
170 }
171
172 // Older is rejected.
173 ev3 := makeEv(t, s, 0, now-1, nil, `{"name":"ancient"}`)
174 r = p.Ingest(ev3)
175 if r.OK {
176 t.Fatal("older replaceable should be rejected")
177 }
178 }
179
180 func TestIngestParamReplaceable(t *testing.T) {
181 p, s := setup(t)
182 now := time.Now().Unix()
183
184 mkTags := func(dval string) *tag.S {
185 return tag.NewS(tag.NewFromBytesSlice([]byte("d"), []byte(dval)))
186 }
187
188 ev1 := makeEv(t, s, 30023, now, mkTags("article-1"), "v1")
189 r := p.Ingest(ev1)
190 if !r.OK {
191 t.Fatalf("first param replaceable failed: %s", r.Reason)
192 }
193
194 // Same d-tag, newer — replaces.
195 ev2 := makeEv(t, s, 30023, now+1, mkTags("article-1"), "v2")
196 r = p.Ingest(ev2)
197 if !r.OK {
198 t.Fatalf("newer param replaceable failed: %s", r.Reason)
199 }
200
201 // Same d-tag, older — rejected.
202 ev3 := makeEv(t, s, 30023, now-1, mkTags("article-1"), "v0")
203 r = p.Ingest(ev3)
204 if r.OK {
205 t.Fatal("older param replaceable should be rejected")
206 }
207
208 // Different d-tag — accepted (independent).
209 ev4 := makeEv(t, s, 30023, now, mkTags("article-2"), "other")
210 r = p.Ingest(ev4)
211 if !r.OK {
212 t.Fatalf("different d-tag should be accepted: %s", r.Reason)
213 }
214 }
215
216 func TestIngestExpired(t *testing.T) {
217 p, s := setup(t)
218 past := time.Now().Unix() - 60
219 expTags := tag.NewS(tag.NewFromBytesSlice(
220 []byte("expiration"), []byte("1000000000"),
221 ))
222 ev := makeEv(t, s, 1, past, expTags, "expired")
223 r := p.Ingest(ev)
224 if r.OK {
225 t.Fatal("expired event should be rejected")
226 }
227 }
228
229 func TestIngestDeletion(t *testing.T) {
230 p, s := setup(t)
231 now := time.Now().Unix()
232
233 // Store a target event.
234 target := makeEv(t, s, 1, now, nil, "to be deleted")
235 r := p.Ingest(target)
236 if !r.OK {
237 t.Fatalf("target ingest failed: %s", r.Reason)
238 }
239
240 // Create deletion event referencing the target.
241 eTag := tag.NewFromBytesSlice([]byte("e"), target.ID)
242 delTags := tag.NewS(eTag)
243 del := makeEv(t, s, kind.EventDeletion.K, now+1, delTags, "")
244 r = p.Ingest(del)
245 if !r.OK {
246 t.Fatalf("deletion ingest failed: %s", r.Reason)
247 }
248 }
249
250 func TestIngestFutureTimestamp(t *testing.T) {
251 p, s := setup(t)
252 future := time.Now().Unix() + 3600 // 1 hour ahead
253 ev := makeEv(t, s, 1, future, nil, "future")
254 r := p.Ingest(ev)
255 if r.OK {
256 t.Fatal("event too far in future should be rejected")
257 }
258 }
259
260 func TestIngestIDMismatch(t *testing.T) {
261 p, s := setup(t)
262 ev := makeEv(t, s, 1, time.Now().Unix(), nil, "hello")
263 // Corrupt the ID without corrupting the sig.
264 ev.ID[0] ^= 0xFF
265 r := p.Ingest(ev)
266 if r.OK {
267 t.Fatal("event with mismatched ID should be rejected")
268 }
269 }
270