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