summaryrefslogtreecommitdiffstats
path: root/relay_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'relay_test.go')
-rw-r--r--relay_test.go333
1 files changed, 333 insertions, 0 deletions
diff --git a/relay_test.go b/relay_test.go
new file mode 100644
index 0000000..4ace956
--- /dev/null
+++ b/relay_test.go
@@ -0,0 +1,333 @@
1package nostr
2
3import (
4 "context"
5 "encoding/json"
6 "net/http"
7 "net/http/httptest"
8 "strings"
9 "testing"
10 "time"
11
12 "github.com/coder/websocket"
13)
14
15// mockRelay creates a test WebSocket server that echoes messages
16func mockRelay(t *testing.T, handler func(conn *websocket.Conn)) *httptest.Server {
17 return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
18 conn, err := websocket.Accept(w, r, nil)
19 if err != nil {
20 t.Logf("Failed to accept WebSocket: %v", err)
21 return
22 }
23 defer conn.Close(websocket.StatusNormalClosure, "")
24
25 handler(conn)
26 }))
27}
28
29func TestConnect(t *testing.T) {
30 server := mockRelay(t, func(conn *websocket.Conn) {
31 // Just accept and wait
32 time.Sleep(100 * time.Millisecond)
33 })
34 defer server.Close()
35
36 url := "ws" + strings.TrimPrefix(server.URL, "http")
37 ctx := context.Background()
38
39 relay, err := Connect(ctx, url)
40 if err != nil {
41 t.Fatalf("Connect() error = %v", err)
42 }
43 defer relay.Close()
44
45 if relay.URL != url {
46 t.Errorf("Relay.URL = %s, want %s", relay.URL, url)
47 }
48}
49
50func TestConnectError(t *testing.T) {
51 ctx := context.Background()
52 _, err := Connect(ctx, "ws://localhost:99999")
53 if err == nil {
54 t.Error("Connect() expected error for invalid URL")
55 }
56}
57
58func TestRelaySendReceive(t *testing.T) {
59 server := mockRelay(t, func(conn *websocket.Conn) {
60 // Read message
61 _, data, err := conn.Read(context.Background())
62 if err != nil {
63 t.Logf("Read error: %v", err)
64 return
65 }
66
67 // Echo it back as NOTICE
68 var arr []interface{}
69 json.Unmarshal(data, &arr)
70
71 response, _ := json.Marshal([]interface{}{"NOTICE", "received: " + arr[0].(string)})
72 conn.Write(context.Background(), websocket.MessageText, response)
73 })
74 defer server.Close()
75
76 url := "ws" + strings.TrimPrefix(server.URL, "http")
77 ctx := context.Background()
78
79 relay, err := Connect(ctx, url)
80 if err != nil {
81 t.Fatalf("Connect() error = %v", err)
82 }
83 defer relay.Close()
84
85 // Send a CLOSE envelope
86 closeEnv := CloseEnvelope{SubscriptionID: "test"}
87 if err := relay.Send(ctx, closeEnv); err != nil {
88 t.Fatalf("Send() error = %v", err)
89 }
90
91 // Receive response
92 env, err := relay.Receive(ctx)
93 if err != nil {
94 t.Fatalf("Receive() error = %v", err)
95 }
96
97 noticeEnv, ok := env.(*NoticeEnvelope)
98 if !ok {
99 t.Fatalf("Expected *NoticeEnvelope, got %T", env)
100 }
101
102 if !strings.Contains(noticeEnv.Message, "CLOSE") {
103 t.Errorf("Message = %s, want to contain 'CLOSE'", noticeEnv.Message)
104 }
105}
106
107func TestRelayPublish(t *testing.T) {
108 server := mockRelay(t, func(conn *websocket.Conn) {
109 // Read the EVENT message
110 _, data, err := conn.Read(context.Background())
111 if err != nil {
112 t.Logf("Read error: %v", err)
113 return
114 }
115
116 // Parse to get event ID
117 var arr []json.RawMessage
118 json.Unmarshal(data, &arr)
119
120 var event Event
121 json.Unmarshal(arr[1], &event)
122
123 // Send OK response
124 response, _ := json.Marshal([]interface{}{"OK", event.ID, true, ""})
125 conn.Write(context.Background(), websocket.MessageText, response)
126 })
127 defer server.Close()
128
129 url := "ws" + strings.TrimPrefix(server.URL, "http")
130 ctx := context.Background()
131
132 relay, err := Connect(ctx, url)
133 if err != nil {
134 t.Fatalf("Connect() error = %v", err)
135 }
136 defer relay.Close()
137
138 // Create and sign event
139 key, _ := GenerateKey()
140 event := &Event{
141 CreatedAt: time.Now().Unix(),
142 Kind: KindTextNote,
143 Tags: Tags{},
144 Content: "Test publish",
145 }
146 key.Sign(event)
147
148 // Publish
149 if err := relay.Publish(ctx, event); err != nil {
150 t.Fatalf("Publish() error = %v", err)
151 }
152}
153
154func TestRelayPublishRejected(t *testing.T) {
155 server := mockRelay(t, func(conn *websocket.Conn) {
156 // Read the EVENT message
157 _, data, err := conn.Read(context.Background())
158 if err != nil {
159 return
160 }
161
162 var arr []json.RawMessage
163 json.Unmarshal(data, &arr)
164
165 var event Event
166 json.Unmarshal(arr[1], &event)
167
168 // Send rejection
169 response, _ := json.Marshal([]interface{}{"OK", event.ID, false, "blocked: spam"})
170 conn.Write(context.Background(), websocket.MessageText, response)
171 })
172 defer server.Close()
173
174 url := "ws" + strings.TrimPrefix(server.URL, "http")
175 ctx := context.Background()
176
177 relay, err := Connect(ctx, url)
178 if err != nil {
179 t.Fatalf("Connect() error = %v", err)
180 }
181 defer relay.Close()
182
183 key, _ := GenerateKey()
184 event := &Event{
185 CreatedAt: time.Now().Unix(),
186 Kind: KindTextNote,
187 Tags: Tags{},
188 Content: "Test",
189 }
190 key.Sign(event)
191
192 err = relay.Publish(ctx, event)
193 if err == nil {
194 t.Error("Publish() expected error for rejected event")
195 }
196 if !strings.Contains(err.Error(), "rejected") {
197 t.Errorf("Error = %v, want to contain 'rejected'", err)
198 }
199}
200
201func TestRelaySubscribe(t *testing.T) {
202 server := mockRelay(t, func(conn *websocket.Conn) {
203 // Read REQ
204 _, data, err := conn.Read(context.Background())
205 if err != nil {
206 return
207 }
208
209 var arr []json.RawMessage
210 json.Unmarshal(data, &arr)
211
212 var subID string
213 json.Unmarshal(arr[1], &subID)
214
215 // Send some events
216 for i := 0; i < 3; i++ {
217 event := Event{
218 ID: "event" + string(rune('0'+i)),
219 PubKey: "pubkey",
220 CreatedAt: time.Now().Unix(),
221 Kind: 1,
222 Tags: Tags{},
223 Content: "Test event",
224 Sig: "sig",
225 }
226 response, _ := json.Marshal([]interface{}{"EVENT", subID, event})
227 conn.Write(context.Background(), websocket.MessageText, response)
228 }
229
230 // Send EOSE
231 eose, _ := json.Marshal([]interface{}{"EOSE", subID})
232 conn.Write(context.Background(), websocket.MessageText, eose)
233 })
234 defer server.Close()
235
236 url := "ws" + strings.TrimPrefix(server.URL, "http")
237 ctx := context.Background()
238
239 relay, err := Connect(ctx, url)
240 if err != nil {
241 t.Fatalf("Connect() error = %v", err)
242 }
243 defer relay.Close()
244
245 sub, err := relay.Subscribe(ctx, "sub1", Filter{Kinds: []int{1}})
246 if err != nil {
247 t.Fatalf("Subscribe() error = %v", err)
248 }
249
250 // Start listening in background
251 go relay.Listen(ctx)
252
253 // Collect events
254 eventCount := 0
255 timeout := time.After(2 * time.Second)
256
257 for {
258 select {
259 case <-sub.Events:
260 eventCount++
261 case <-sub.EOSE:
262 if eventCount != 3 {
263 t.Errorf("Received %d events, want 3", eventCount)
264 }
265 return
266 case <-timeout:
267 t.Fatal("Timeout waiting for events")
268 }
269 }
270}
271
272func TestRelayClose(t *testing.T) {
273 server := mockRelay(t, func(conn *websocket.Conn) {
274 time.Sleep(100 * time.Millisecond)
275 })
276 defer server.Close()
277
278 url := "ws" + strings.TrimPrefix(server.URL, "http")
279 ctx := context.Background()
280
281 relay, err := Connect(ctx, url)
282 if err != nil {
283 t.Fatalf("Connect() error = %v", err)
284 }
285
286 if err := relay.Close(); err != nil {
287 t.Errorf("Close() error = %v", err)
288 }
289
290 // Second close should be safe
291 if err := relay.Close(); err != nil {
292 t.Errorf("Second Close() error = %v", err)
293 }
294}
295
296func TestSubscriptionClose(t *testing.T) {
297 server := mockRelay(t, func(conn *websocket.Conn) {
298 // Read REQ
299 conn.Read(context.Background())
300
301 // Wait for CLOSE
302 _, data, err := conn.Read(context.Background())
303 if err != nil {
304 return
305 }
306
307 var arr []interface{}
308 json.Unmarshal(data, &arr)
309
310 if arr[0] != "CLOSE" {
311 t.Errorf("Expected CLOSE, got %v", arr[0])
312 }
313 })
314 defer server.Close()
315
316 url := "ws" + strings.TrimPrefix(server.URL, "http")
317 ctx := context.Background()
318
319 relay, err := Connect(ctx, url)
320 if err != nil {
321 t.Fatalf("Connect() error = %v", err)
322 }
323 defer relay.Close()
324
325 sub, err := relay.Subscribe(ctx, "sub1", Filter{Kinds: []int{1}})
326 if err != nil {
327 t.Fatalf("Subscribe() error = %v", err)
328 }
329
330 if err := sub.Close(ctx); err != nil {
331 t.Errorf("Subscription.Close() error = %v", err)
332 }
333}