Implement spider functionality for event synchronization
- Introduced a new `spider` package to manage connections to admin relays and synchronize events for followed pubkeys. - Added configuration options for spider mode in the application settings, allowing for different operational modes (e.g., follows). - Implemented callback mechanisms to dynamically retrieve admin relays and follow lists. - Enhanced the main application to initialize and manage the spider, including starting and stopping its operation. - Added tests to validate spider creation, callbacks, and operational behavior. - Bumped version to v0.17.14.
This commit is contained in:
244
pkg/spider/spider_test.go
Normal file
244
pkg/spider/spider_test.go
Normal file
@@ -0,0 +1,244 @@
|
||||
package spider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"next.orly.dev/pkg/database"
|
||||
)
|
||||
|
||||
func TestSpiderCreation(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// Create a temporary database for testing
|
||||
tempDir, err := os.MkdirTemp("", "spider-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
db, err := database.New(ctx, cancel, tempDir, "error")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create test database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Test spider creation
|
||||
spider, err := New(ctx, db, nil, "follows")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create spider: %v", err)
|
||||
}
|
||||
|
||||
if spider == nil {
|
||||
t.Fatal("Spider is nil")
|
||||
}
|
||||
|
||||
// Test that spider is not running initially
|
||||
spider.mu.RLock()
|
||||
running := spider.running
|
||||
spider.mu.RUnlock()
|
||||
|
||||
if running {
|
||||
t.Error("Spider should not be running initially")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpiderCallbacks(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// Create a temporary database for testing
|
||||
tempDir, err := os.MkdirTemp("", "spider-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
db, err := database.New(ctx, cancel, tempDir, "error")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create test database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
spider, err := New(ctx, db, nil, "follows")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create spider: %v", err)
|
||||
}
|
||||
|
||||
// Test callback setup
|
||||
testRelays := []string{"wss://relay1.example.com", "wss://relay2.example.com"}
|
||||
testPubkeys := [][]byte{{1, 2, 3}, {4, 5, 6}}
|
||||
|
||||
spider.SetCallbacks(
|
||||
func() []string { return testRelays },
|
||||
func() [][]byte { return testPubkeys },
|
||||
)
|
||||
|
||||
// Verify callbacks are set
|
||||
spider.mu.RLock()
|
||||
hasCallbacks := spider.getAdminRelays != nil && spider.getFollowList != nil
|
||||
spider.mu.RUnlock()
|
||||
|
||||
if !hasCallbacks {
|
||||
t.Error("Callbacks should be set")
|
||||
}
|
||||
|
||||
// Test that start fails without callbacks being set first
|
||||
spider2, err := New(ctx, db, nil, "follows")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create second spider: %v", err)
|
||||
}
|
||||
|
||||
err = spider2.Start()
|
||||
if err == nil {
|
||||
t.Error("Start should fail when callbacks are not set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpiderModeValidation(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// Create a temporary database for testing
|
||||
tempDir, err := os.MkdirTemp("", "spider-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
db, err := database.New(ctx, cancel, tempDir, "error")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create test database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Test valid mode
|
||||
spider, err := New(ctx, db, nil, "follows")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create spider with valid mode: %v", err)
|
||||
}
|
||||
if spider == nil {
|
||||
t.Fatal("Spider should not be nil for valid mode")
|
||||
}
|
||||
|
||||
// Test invalid mode
|
||||
_, err = New(ctx, db, nil, "invalid")
|
||||
if err == nil {
|
||||
t.Error("Should fail with invalid mode")
|
||||
}
|
||||
|
||||
// Test none mode (should succeed but be a no-op)
|
||||
spider2, err := New(ctx, db, nil, "none")
|
||||
if err != nil {
|
||||
t.Errorf("Should succeed with 'none' mode: %v", err)
|
||||
}
|
||||
if spider2 == nil {
|
||||
t.Error("Spider should not be nil for 'none' mode")
|
||||
}
|
||||
|
||||
// Test that 'none' mode doesn't require callbacks
|
||||
err = spider2.Start()
|
||||
if err != nil {
|
||||
t.Errorf("'none' mode should start without callbacks: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpiderBatching(t *testing.T) {
|
||||
// Test batch creation logic
|
||||
followList := make([][]byte, 50) // 50 pubkeys
|
||||
for i := range followList {
|
||||
followList[i] = make([]byte, 32)
|
||||
for j := range followList[i] {
|
||||
followList[i][j] = byte(i)
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
rc := &RelayConnection{
|
||||
url: "wss://test.relay.com",
|
||||
ctx: ctx,
|
||||
}
|
||||
|
||||
batches := rc.createBatches(followList)
|
||||
|
||||
// Should create 3 batches: 20, 20, 10
|
||||
expectedBatches := 3
|
||||
if len(batches) != expectedBatches {
|
||||
t.Errorf("Expected %d batches, got %d", expectedBatches, len(batches))
|
||||
}
|
||||
|
||||
// Check batch sizes
|
||||
if len(batches[0]) != BatchSize {
|
||||
t.Errorf("First batch should have %d pubkeys, got %d", BatchSize, len(batches[0]))
|
||||
}
|
||||
if len(batches[1]) != BatchSize {
|
||||
t.Errorf("Second batch should have %d pubkeys, got %d", BatchSize, len(batches[1]))
|
||||
}
|
||||
if len(batches[2]) != 10 {
|
||||
t.Errorf("Third batch should have 10 pubkeys, got %d", len(batches[2]))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpiderStartStop(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// Create a temporary database for testing
|
||||
tempDir, err := os.MkdirTemp("", "spider-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
db, err := database.New(ctx, cancel, tempDir, "error")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create test database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
spider, err := New(ctx, db, nil, "follows")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create spider: %v", err)
|
||||
}
|
||||
|
||||
// Set up callbacks
|
||||
spider.SetCallbacks(
|
||||
func() []string { return []string{"wss://test.relay.com"} },
|
||||
func() [][]byte { return [][]byte{{1, 2, 3}} },
|
||||
)
|
||||
|
||||
// Test start
|
||||
err = spider.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start spider: %v", err)
|
||||
}
|
||||
|
||||
// Verify spider is running
|
||||
spider.mu.RLock()
|
||||
running := spider.running
|
||||
spider.mu.RUnlock()
|
||||
|
||||
if !running {
|
||||
t.Error("Spider should be running after start")
|
||||
}
|
||||
|
||||
// Test stop
|
||||
spider.Stop()
|
||||
|
||||
// Give it a moment to stop
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Verify spider is stopped
|
||||
spider.mu.RLock()
|
||||
running = spider.running
|
||||
spider.mu.RUnlock()
|
||||
|
||||
if running {
|
||||
t.Error("Spider should not be running after stop")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user