spider_test.go raw

   1  package spider
   2  
   3  import (
   4  	"context"
   5  	"os"
   6  	"testing"
   7  	"time"
   8  
   9  	"next.orly.dev/pkg/database"
  10  )
  11  
  12  func TestSpiderCreation(t *testing.T) {
  13  	ctx, cancel := context.WithCancel(context.Background())
  14  	defer cancel()
  15  
  16  	// Create a temporary database for testing
  17  	tempDir, err := os.MkdirTemp("", "spider-test-*")
  18  	if err != nil {
  19  		t.Fatalf("Failed to create temp dir: %v", err)
  20  	}
  21  	defer os.RemoveAll(tempDir)
  22  
  23  	db, err := database.New(ctx, cancel, tempDir, "error")
  24  	if err != nil {
  25  		t.Fatalf("Failed to create test database: %v", err)
  26  	}
  27  	defer db.Close()
  28  
  29  	// Test spider creation
  30  	spider, err := New(ctx, db, nil, "follows")
  31  	if err != nil {
  32  		t.Fatalf("Failed to create spider: %v", err)
  33  	}
  34  
  35  	if spider == nil {
  36  		t.Fatal("Spider is nil")
  37  	}
  38  
  39  	// Test that spider is not running initially
  40  	spider.mu.RLock()
  41  	running := spider.running
  42  	spider.mu.RUnlock()
  43  
  44  	if running {
  45  		t.Error("Spider should not be running initially")
  46  	}
  47  }
  48  
  49  func TestSpiderCallbacks(t *testing.T) {
  50  	ctx, cancel := context.WithCancel(context.Background())
  51  	defer cancel()
  52  
  53  	// Create a temporary database for testing
  54  	tempDir, err := os.MkdirTemp("", "spider-test-*")
  55  	if err != nil {
  56  		t.Fatalf("Failed to create temp dir: %v", err)
  57  	}
  58  	defer os.RemoveAll(tempDir)
  59  
  60  	db, err := database.New(ctx, cancel, tempDir, "error")
  61  	if err != nil {
  62  		t.Fatalf("Failed to create test database: %v", err)
  63  	}
  64  	defer db.Close()
  65  
  66  	spider, err := New(ctx, db, nil, "follows")
  67  	if err != nil {
  68  		t.Fatalf("Failed to create spider: %v", err)
  69  	}
  70  
  71  	// Test callback setup
  72  	testRelays := []string{"wss://relay1.example.com", "wss://relay2.example.com"}
  73  	testPubkeys := [][]byte{{1, 2, 3}, {4, 5, 6}}
  74  
  75  	spider.SetCallbacks(
  76  		func() []string { return testRelays },
  77  		func() [][]byte { return testPubkeys },
  78  	)
  79  
  80  	// Verify callbacks are set
  81  	spider.mu.RLock()
  82  	hasCallbacks := spider.getAdminRelays != nil && spider.getFollowList != nil
  83  	spider.mu.RUnlock()
  84  
  85  	if !hasCallbacks {
  86  		t.Error("Callbacks should be set")
  87  	}
  88  
  89  	// Test that start fails without callbacks being set first
  90  	spider2, err := New(ctx, db, nil, "follows")
  91  	if err != nil {
  92  		t.Fatalf("Failed to create second spider: %v", err)
  93  	}
  94  
  95  	err = spider2.Start()
  96  	if err == nil {
  97  		t.Error("Start should fail when callbacks are not set")
  98  	}
  99  }
 100  
 101  func TestSpiderModeValidation(t *testing.T) {
 102  	ctx, cancel := context.WithCancel(context.Background())
 103  	defer cancel()
 104  
 105  	// Create a temporary database for testing
 106  	tempDir, err := os.MkdirTemp("", "spider-test-*")
 107  	if err != nil {
 108  		t.Fatalf("Failed to create temp dir: %v", err)
 109  	}
 110  	defer os.RemoveAll(tempDir)
 111  
 112  	db, err := database.New(ctx, cancel, tempDir, "error")
 113  	if err != nil {
 114  		t.Fatalf("Failed to create test database: %v", err)
 115  	}
 116  	defer db.Close()
 117  
 118  	// Test valid mode
 119  	spider, err := New(ctx, db, nil, "follows")
 120  	if err != nil {
 121  		t.Fatalf("Failed to create spider with valid mode: %v", err)
 122  	}
 123  	if spider == nil {
 124  		t.Fatal("Spider should not be nil for valid mode")
 125  	}
 126  
 127  	// Test invalid mode
 128  	_, err = New(ctx, db, nil, "invalid")
 129  	if err == nil {
 130  		t.Error("Should fail with invalid mode")
 131  	}
 132  
 133  	// Test none mode (should succeed but be a no-op)
 134  	spider2, err := New(ctx, db, nil, "none")
 135  	if err != nil {
 136  		t.Errorf("Should succeed with 'none' mode: %v", err)
 137  	}
 138  	if spider2 == nil {
 139  		t.Error("Spider should not be nil for 'none' mode")
 140  	}
 141  
 142  	// Test that 'none' mode doesn't require callbacks
 143  	err = spider2.Start()
 144  	if err != nil {
 145  		t.Errorf("'none' mode should start without callbacks: %v", err)
 146  	}
 147  }
 148  
 149  func TestSpiderBatching(t *testing.T) {
 150  	// Test batch creation logic
 151  	followList := make([][]byte, 50) // 50 pubkeys
 152  	for i := range followList {
 153  		followList[i] = make([]byte, 32)
 154  		for j := range followList[i] {
 155  			followList[i][j] = byte(i)
 156  		}
 157  	}
 158  
 159  	ctx, cancel := context.WithCancel(context.Background())
 160  	defer cancel()
 161  
 162  	rc := &RelayConnection{
 163  		url: "wss://test.relay.com",
 164  		ctx: ctx,
 165  	}
 166  
 167  	batches := rc.createBatches(followList)
 168  
 169  	// Should create 3 batches: 20, 20, 10
 170  	expectedBatches := 3
 171  	if len(batches) != expectedBatches {
 172  		t.Errorf("Expected %d batches, got %d", expectedBatches, len(batches))
 173  	}
 174  
 175  	// Check batch sizes
 176  	if len(batches[0]) != BatchSize {
 177  		t.Errorf("First batch should have %d pubkeys, got %d", BatchSize, len(batches[0]))
 178  	}
 179  	if len(batches[1]) != BatchSize {
 180  		t.Errorf("Second batch should have %d pubkeys, got %d", BatchSize, len(batches[1]))
 181  	}
 182  	if len(batches[2]) != 10 {
 183  		t.Errorf("Third batch should have 10 pubkeys, got %d", len(batches[2]))
 184  	}
 185  }
 186  
 187  func TestSpiderStartStop(t *testing.T) {
 188  	ctx, cancel := context.WithCancel(context.Background())
 189  	defer cancel()
 190  
 191  	// Create a temporary database for testing
 192  	tempDir, err := os.MkdirTemp("", "spider-test-*")
 193  	if err != nil {
 194  		t.Fatalf("Failed to create temp dir: %v", err)
 195  	}
 196  	defer os.RemoveAll(tempDir)
 197  
 198  	db, err := database.New(ctx, cancel, tempDir, "error")
 199  	if err != nil {
 200  		t.Fatalf("Failed to create test database: %v", err)
 201  	}
 202  	defer db.Close()
 203  
 204  	spider, err := New(ctx, db, nil, "follows")
 205  	if err != nil {
 206  		t.Fatalf("Failed to create spider: %v", err)
 207  	}
 208  
 209  	// Set up callbacks
 210  	spider.SetCallbacks(
 211  		func() []string { return []string{"wss://test.relay.com"} },
 212  		func() [][]byte { return [][]byte{{1, 2, 3}} },
 213  	)
 214  
 215  	// Test start
 216  	err = spider.Start()
 217  	if err != nil {
 218  		t.Fatalf("Failed to start spider: %v", err)
 219  	}
 220  
 221  	// Verify spider is running
 222  	spider.mu.RLock()
 223  	running := spider.running
 224  	spider.mu.RUnlock()
 225  
 226  	if !running {
 227  		t.Error("Spider should be running after start")
 228  	}
 229  
 230  	// Test stop
 231  	spider.Stop()
 232  
 233  	// Give it a moment to stop
 234  	time.Sleep(100 * time.Millisecond)
 235  
 236  	// Verify spider is stopped
 237  	spider.mu.RLock()
 238  	running = spider.running
 239  	spider.mu.RUnlock()
 240  
 241  	if running {
 242  		t.Error("Spider should not be running after stop")
 243  	}
 244  }
 245