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