processing_test.go raw
1 package processing
2
3 import (
4 "context"
5 "errors"
6 "testing"
7
8 "next.orly.dev/pkg/nostr/encoders/event"
9 )
10
11 // mockDatabase is a mock implementation of Database for testing.
12 type mockDatabase struct {
13 saveErr error
14 saveExists bool
15 checkErr error
16 }
17
18 func (m *mockDatabase) SaveEvent(ctx context.Context, ev *event.E) (exists bool, err error) {
19 return m.saveExists, m.saveErr
20 }
21
22 func (m *mockDatabase) CheckForDeleted(ev *event.E, adminOwners [][]byte) error {
23 return m.checkErr
24 }
25
26 // mockPublisher is a mock implementation of Publisher for testing.
27 type mockPublisher struct {
28 deliveredEvents []*event.E
29 }
30
31 func (m *mockPublisher) Deliver(ev *event.E) {
32 m.deliveredEvents = append(m.deliveredEvents, ev)
33 }
34
35 // mockRateLimiter is a mock implementation of RateLimiter for testing.
36 type mockRateLimiter struct {
37 enabled bool
38 waitCalled bool
39 }
40
41 func (m *mockRateLimiter) IsEnabled() bool {
42 return m.enabled
43 }
44
45 func (m *mockRateLimiter) Wait(ctx context.Context, opType int) error {
46 m.waitCalled = true
47 return nil
48 }
49
50 // mockSyncManager is a mock implementation of SyncManager for testing.
51 type mockSyncManager struct {
52 updateCalled bool
53 }
54
55 func (m *mockSyncManager) UpdateSerial() {
56 m.updateCalled = true
57 }
58
59 // mockACLRegistry is a mock implementation of ACLRegistry for testing.
60 type mockACLRegistry struct {
61 active string
62 configureCalls int
63 }
64
65 func (m *mockACLRegistry) Configure(cfg ...any) error {
66 m.configureCalls++
67 return nil
68 }
69
70 func (m *mockACLRegistry) Active() string {
71 return m.active
72 }
73
74 func TestNew(t *testing.T) {
75 db := &mockDatabase{}
76 pub := &mockPublisher{}
77
78 s := New(nil, db, pub)
79 if s == nil {
80 t.Fatal("New() returned nil")
81 }
82 if s.cfg == nil {
83 t.Fatal("cfg should be set to default")
84 }
85 if s.db != db {
86 t.Fatal("db not set correctly")
87 }
88 if s.publisher != pub {
89 t.Fatal("publisher not set correctly")
90 }
91 }
92
93 func TestDefaultConfig(t *testing.T) {
94 cfg := DefaultConfig()
95 if cfg.WriteTimeout != 30*1e9 {
96 t.Errorf("expected WriteTimeout=30s, got %v", cfg.WriteTimeout)
97 }
98 }
99
100 func TestResultConstructors(t *testing.T) {
101 // OK
102 r := OK()
103 if !r.Saved || r.Error != nil || r.Blocked {
104 t.Error("OK() should return Saved=true")
105 }
106
107 // Blocked
108 r = Blocked("test blocked")
109 if r.Saved || !r.Blocked || r.BlockMsg != "test blocked" {
110 t.Error("Blocked() should return Blocked=true with message")
111 }
112
113 // Failed
114 err := errors.New("test error")
115 r = Failed(err)
116 if r.Saved || r.Error != err {
117 t.Error("Failed() should return Error set")
118 }
119 }
120
121 func TestProcess_Success(t *testing.T) {
122 db := &mockDatabase{}
123 pub := &mockPublisher{}
124
125 s := New(nil, db, pub)
126
127 ev := event.New()
128 ev.Kind = 1
129 ev.Pubkey = make([]byte, 32)
130
131 result := s.Process(context.Background(), ev)
132 if !result.Saved {
133 t.Errorf("should save successfully: %v", result.Error)
134 }
135 }
136
137 func TestProcess_DatabaseError(t *testing.T) {
138 testErr := errors.New("db error")
139 db := &mockDatabase{saveErr: testErr}
140 pub := &mockPublisher{}
141
142 s := New(nil, db, pub)
143
144 ev := event.New()
145 ev.Kind = 1
146 ev.Pubkey = make([]byte, 32)
147
148 result := s.Process(context.Background(), ev)
149 if result.Saved {
150 t.Error("should not save on error")
151 }
152 if result.Error != testErr {
153 t.Error("should return the database error")
154 }
155 }
156
157 func TestProcess_BlockedError(t *testing.T) {
158 db := &mockDatabase{saveErr: errors.New("blocked: event already deleted")}
159 pub := &mockPublisher{}
160
161 s := New(nil, db, pub)
162
163 ev := event.New()
164 ev.Kind = 1
165 ev.Pubkey = make([]byte, 32)
166
167 result := s.Process(context.Background(), ev)
168 if result.Saved {
169 t.Error("should not save blocked events")
170 }
171 if !result.Blocked {
172 t.Error("should mark as blocked")
173 }
174 if result.BlockMsg != "event already deleted" {
175 t.Errorf("expected block message, got: %s", result.BlockMsg)
176 }
177 }
178
179 func TestProcess_WithRateLimiter(t *testing.T) {
180 db := &mockDatabase{}
181 pub := &mockPublisher{}
182 rl := &mockRateLimiter{enabled: true}
183
184 s := New(nil, db, pub)
185 s.SetRateLimiter(rl)
186
187 ev := event.New()
188 ev.Kind = 1
189 ev.Pubkey = make([]byte, 32)
190
191 s.Process(context.Background(), ev)
192
193 if !rl.waitCalled {
194 t.Error("rate limiter Wait should be called")
195 }
196 }
197
198 func TestProcess_WithSyncManager(t *testing.T) {
199 db := &mockDatabase{}
200 pub := &mockPublisher{}
201 sm := &mockSyncManager{}
202
203 s := New(nil, db, pub)
204 s.SetSyncManager(sm)
205
206 ev := event.New()
207 ev.Kind = 1
208 ev.Pubkey = make([]byte, 32)
209
210 s.Process(context.Background(), ev)
211
212 if !sm.updateCalled {
213 t.Error("sync manager UpdateSerial should be called")
214 }
215 }
216
217 func TestProcess_AdminFollowListTriggersACLReconfigure(t *testing.T) {
218 db := &mockDatabase{}
219 pub := &mockPublisher{}
220 acl := &mockACLRegistry{active: "follows"}
221
222 adminPubkey := make([]byte, 32)
223 for i := range adminPubkey {
224 adminPubkey[i] = byte(i)
225 }
226
227 cfg := &Config{
228 Admins: [][]byte{adminPubkey},
229 }
230
231 s := New(cfg, db, pub)
232 s.SetACLRegistry(acl)
233
234 ev := event.New()
235 ev.Kind = 3 // FollowList
236 ev.Pubkey = adminPubkey
237
238 s.Process(context.Background(), ev)
239
240 // Give goroutine time to run
241 // In production this would be tested differently
242 // For now just verify the path is exercised
243 }
244
245 func TestSetters(t *testing.T) {
246 db := &mockDatabase{}
247 pub := &mockPublisher{}
248 s := New(nil, db, pub)
249
250 rl := &mockRateLimiter{}
251 s.SetRateLimiter(rl)
252 if s.rateLimiter != rl {
253 t.Error("SetRateLimiter should set rateLimiter")
254 }
255
256 sm := &mockSyncManager{}
257 s.SetSyncManager(sm)
258 if s.syncManager != sm {
259 t.Error("SetSyncManager should set syncManager")
260 }
261
262 acl := &mockACLRegistry{}
263 s.SetACLRegistry(acl)
264 if s.aclRegistry != acl {
265 t.Error("SetACLRegistry should set aclRegistry")
266 }
267 }
268
269 func TestIsAdminEvent(t *testing.T) {
270 adminPubkey := make([]byte, 32)
271 for i := range adminPubkey {
272 adminPubkey[i] = byte(i)
273 }
274
275 ownerPubkey := make([]byte, 32)
276 for i := range ownerPubkey {
277 ownerPubkey[i] = byte(i + 50)
278 }
279
280 cfg := &Config{
281 Admins: [][]byte{adminPubkey},
282 Owners: [][]byte{ownerPubkey},
283 }
284
285 s := New(cfg, &mockDatabase{}, &mockPublisher{})
286
287 // Admin pubkey
288 if !s.isAdminPubkey(adminPubkey) {
289 t.Error("should recognize admin pubkey")
290 }
291
292 // Owner pubkey
293 if !s.isOwnerPubkey(ownerPubkey) {
294 t.Error("should recognize owner pubkey")
295 }
296
297 // Regular pubkey
298 regularPubkey := make([]byte, 32)
299 for i := range regularPubkey {
300 regularPubkey[i] = byte(i + 100)
301 }
302 if s.isAdminPubkey(regularPubkey) {
303 t.Error("should not recognize regular pubkey as admin")
304 }
305 if s.isOwnerPubkey(regularPubkey) {
306 t.Error("should not recognize regular pubkey as owner")
307 }
308 }
309
310 func TestFastEqual(t *testing.T) {
311 a := []byte{1, 2, 3, 4}
312 b := []byte{1, 2, 3, 4}
313 c := []byte{1, 2, 3, 5}
314 d := []byte{1, 2, 3}
315
316 if !fastEqual(a, b) {
317 t.Error("equal slices should return true")
318 }
319 if fastEqual(a, c) {
320 t.Error("different values should return false")
321 }
322 if fastEqual(a, d) {
323 t.Error("different lengths should return false")
324 }
325 }
326