You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

325 lines
6.6 KiB

package processing
import (
"context"
"errors"
"testing"
"git.mleku.dev/mleku/nostr/encoders/event"
)
// mockDatabase is a mock implementation of Database for testing.
type mockDatabase struct {
saveErr error
saveExists bool
checkErr error
}
func (m *mockDatabase) SaveEvent(ctx context.Context, ev *event.E) (exists bool, err error) {
return m.saveExists, m.saveErr
}
func (m *mockDatabase) CheckForDeleted(ev *event.E, adminOwners [][]byte) error {
return m.checkErr
}
// mockPublisher is a mock implementation of Publisher for testing.
type mockPublisher struct {
deliveredEvents []*event.E
}
func (m *mockPublisher) Deliver(ev *event.E) {
m.deliveredEvents = append(m.deliveredEvents, ev)
}
// mockRateLimiter is a mock implementation of RateLimiter for testing.
type mockRateLimiter struct {
enabled bool
waitCalled bool
}
func (m *mockRateLimiter) IsEnabled() bool {
return m.enabled
}
func (m *mockRateLimiter) Wait(ctx context.Context, opType int) error {
m.waitCalled = true
return nil
}
// mockSyncManager is a mock implementation of SyncManager for testing.
type mockSyncManager struct {
updateCalled bool
}
func (m *mockSyncManager) UpdateSerial() {
m.updateCalled = true
}
// mockACLRegistry is a mock implementation of ACLRegistry for testing.
type mockACLRegistry struct {
active string
configureCalls int
}
func (m *mockACLRegistry) Configure(cfg ...any) error {
m.configureCalls++
return nil
}
func (m *mockACLRegistry) Active() string {
return m.active
}
func TestNew(t *testing.T) {
db := &mockDatabase{}
pub := &mockPublisher{}
s := New(nil, db, pub)
if s == nil {
t.Fatal("New() returned nil")
}
if s.cfg == nil {
t.Fatal("cfg should be set to default")
}
if s.db != db {
t.Fatal("db not set correctly")
}
if s.publisher != pub {
t.Fatal("publisher not set correctly")
}
}
func TestDefaultConfig(t *testing.T) {
cfg := DefaultConfig()
if cfg.WriteTimeout != 30*1e9 {
t.Errorf("expected WriteTimeout=30s, got %v", cfg.WriteTimeout)
}
}
func TestResultConstructors(t *testing.T) {
// OK
r := OK()
if !r.Saved || r.Error != nil || r.Blocked {
t.Error("OK() should return Saved=true")
}
// Blocked
r = Blocked("test blocked")
if r.Saved || !r.Blocked || r.BlockMsg != "test blocked" {
t.Error("Blocked() should return Blocked=true with message")
}
// Failed
err := errors.New("test error")
r = Failed(err)
if r.Saved || r.Error != err {
t.Error("Failed() should return Error set")
}
}
func TestProcess_Success(t *testing.T) {
db := &mockDatabase{}
pub := &mockPublisher{}
s := New(nil, db, pub)
ev := event.New()
ev.Kind = 1
ev.Pubkey = make([]byte, 32)
result := s.Process(context.Background(), ev)
if !result.Saved {
t.Errorf("should save successfully: %v", result.Error)
}
}
func TestProcess_DatabaseError(t *testing.T) {
testErr := errors.New("db error")
db := &mockDatabase{saveErr: testErr}
pub := &mockPublisher{}
s := New(nil, db, pub)
ev := event.New()
ev.Kind = 1
ev.Pubkey = make([]byte, 32)
result := s.Process(context.Background(), ev)
if result.Saved {
t.Error("should not save on error")
}
if result.Error != testErr {
t.Error("should return the database error")
}
}
func TestProcess_BlockedError(t *testing.T) {
db := &mockDatabase{saveErr: errors.New("blocked: event already deleted")}
pub := &mockPublisher{}
s := New(nil, db, pub)
ev := event.New()
ev.Kind = 1
ev.Pubkey = make([]byte, 32)
result := s.Process(context.Background(), ev)
if result.Saved {
t.Error("should not save blocked events")
}
if !result.Blocked {
t.Error("should mark as blocked")
}
if result.BlockMsg != "event already deleted" {
t.Errorf("expected block message, got: %s", result.BlockMsg)
}
}
func TestProcess_WithRateLimiter(t *testing.T) {
db := &mockDatabase{}
pub := &mockPublisher{}
rl := &mockRateLimiter{enabled: true}
s := New(nil, db, pub)
s.SetRateLimiter(rl)
ev := event.New()
ev.Kind = 1
ev.Pubkey = make([]byte, 32)
s.Process(context.Background(), ev)
if !rl.waitCalled {
t.Error("rate limiter Wait should be called")
}
}
func TestProcess_WithSyncManager(t *testing.T) {
db := &mockDatabase{}
pub := &mockPublisher{}
sm := &mockSyncManager{}
s := New(nil, db, pub)
s.SetSyncManager(sm)
ev := event.New()
ev.Kind = 1
ev.Pubkey = make([]byte, 32)
s.Process(context.Background(), ev)
if !sm.updateCalled {
t.Error("sync manager UpdateSerial should be called")
}
}
func TestProcess_AdminFollowListTriggersACLReconfigure(t *testing.T) {
db := &mockDatabase{}
pub := &mockPublisher{}
acl := &mockACLRegistry{active: "follows"}
adminPubkey := make([]byte, 32)
for i := range adminPubkey {
adminPubkey[i] = byte(i)
}
cfg := &Config{
Admins: [][]byte{adminPubkey},
}
s := New(cfg, db, pub)
s.SetACLRegistry(acl)
ev := event.New()
ev.Kind = 3 // FollowList
ev.Pubkey = adminPubkey
s.Process(context.Background(), ev)
// Give goroutine time to run
// In production this would be tested differently
// For now just verify the path is exercised
}
func TestSetters(t *testing.T) {
db := &mockDatabase{}
pub := &mockPublisher{}
s := New(nil, db, pub)
rl := &mockRateLimiter{}
s.SetRateLimiter(rl)
if s.rateLimiter != rl {
t.Error("SetRateLimiter should set rateLimiter")
}
sm := &mockSyncManager{}
s.SetSyncManager(sm)
if s.syncManager != sm {
t.Error("SetSyncManager should set syncManager")
}
acl := &mockACLRegistry{}
s.SetACLRegistry(acl)
if s.aclRegistry != acl {
t.Error("SetACLRegistry should set aclRegistry")
}
}
func TestIsAdminEvent(t *testing.T) {
adminPubkey := make([]byte, 32)
for i := range adminPubkey {
adminPubkey[i] = byte(i)
}
ownerPubkey := make([]byte, 32)
for i := range ownerPubkey {
ownerPubkey[i] = byte(i + 50)
}
cfg := &Config{
Admins: [][]byte{adminPubkey},
Owners: [][]byte{ownerPubkey},
}
s := New(cfg, &mockDatabase{}, &mockPublisher{})
// Admin event
ev := event.New()
ev.Pubkey = adminPubkey
if !s.isAdminEvent(ev) {
t.Error("should recognize admin event")
}
// Owner event
ev.Pubkey = ownerPubkey
if !s.isAdminEvent(ev) {
t.Error("should recognize owner event")
}
// Regular event
ev.Pubkey = make([]byte, 32)
for i := range ev.Pubkey {
ev.Pubkey[i] = byte(i + 100)
}
if s.isAdminEvent(ev) {
t.Error("should not recognize regular event as admin")
}
}
func TestFastEqual(t *testing.T) {
a := []byte{1, 2, 3, 4}
b := []byte{1, 2, 3, 4}
c := []byte{1, 2, 3, 5}
d := []byte{1, 2, 3}
if !fastEqual(a, b) {
t.Error("equal slices should return true")
}
if fastEqual(a, c) {
t.Error("different values should return false")
}
if fastEqual(a, d) {
t.Error("different lengths should return false")
}
}