server_test.mx raw
1 package server
2
3 import (
4 "net"
5 "net/http"
6 "testing"
7 "time"
8
9 "smesh.lol/pkg/acl"
10 "smesh.lol/pkg/nostr/event"
11 "smesh.lol/pkg/nostr/filter"
12 "smesh.lol/pkg/nostr/kind"
13 "smesh.lol/pkg/nostr/signer/p8k"
14 "smesh.lol/pkg/nostr/tag"
15 "smesh.lol/pkg/nostr/ws"
16 "smesh.lol/pkg/relay/pipeline"
17 "smesh.lol/pkg/store"
18 )
19
20 func startRelay(t *testing.T) (addr string) {
21 t.Helper()
22 eng, err := store.Open(t.TempDir())
23 if err != nil {
24 t.Fatal(err)
25 }
26 t.Cleanup(func() { eng.Close() })
27
28 srv := New(eng, acl.Open{}, nil, pipeline.DefaultConfig())
29 ln, err := net.Listen("tcp", "127.0.0.1:0")
30 if err != nil {
31 t.Fatal(err)
32 }
33 t.Cleanup(func() { ln.Close() })
34 go http.Serve(ln, srv)
35 return ln.Addr().String()
36 }
37
38 func TestUpgradeAndClose(t *testing.T) {
39 addr := startRelay(t)
40 c, err := ws.Dial("ws://" + addr)
41 if err != nil {
42 t.Fatalf("dial: %v", err)
43 }
44 c.Close()
45 }
46
47 func TestPublishAndSubscribe(t *testing.T) {
48 addr := startRelay(t)
49 url := "ws://" + addr
50
51 // Client 1: publisher.
52 pub, err := ws.Connect(url)
53 if err != nil {
54 t.Fatalf("connect publisher: %v", err)
55 }
56 defer pub.Close()
57
58 signer := p8k.MustNew()
59 if err := signer.Generate(); err != nil {
60 t.Fatal(err)
61 }
62
63 ev := &event.E{
64 CreatedAt: time.Now().Unix(),
65 Kind: 1,
66 Content: []byte("hello relay"),
67 }
68 if err := ev.Sign(signer); err != nil {
69 t.Fatal(err)
70 }
71
72 if err := pub.Publish(ev); err != nil {
73 t.Fatalf("publish: %v", err)
74 }
75
76 // Allow processing.
77 time.Sleep(100 * time.Millisecond)
78
79 // Client 2: subscriber queries stored events.
80 sub, err := ws.Connect(url)
81 if err != nil {
82 t.Fatalf("connect subscriber: %v", err)
83 }
84 defer sub.Close()
85
86 f := &filter.F{
87 Kinds: kind.NewS(kind.New(uint16(1))),
88 Authors: tag.NewFromBytesSlice(signer.Pub()),
89 }
90 limit := uint(10)
91 f.Limit = &limit
92
93 subscription, err := sub.Subscribe(f)
94 if err != nil {
95 t.Fatalf("subscribe: %v", err)
96 }
97
98 var received []*event.E
99 timeout := time.After(3 * time.Second)
100 loop:
101 for {
102 select {
103 case got, ok := <-subscription.Events:
104 if !ok {
105 break loop
106 }
107 if got != nil {
108 received = append(received, got)
109 }
110 case <-subscription.EOSE:
111 break loop
112 case <-timeout:
113 t.Fatal("timeout waiting for EOSE")
114 }
115 }
116
117 if len(received) != 1 {
118 t.Fatalf("expected 1 event, got %d", len(received))
119 }
120 if string(received[0].Content) != "hello relay" {
121 t.Fatalf("wrong content: %s", received[0].Content)
122 }
123 }
124
125 func TestRealTimeBroadcast(t *testing.T) {
126 addr := startRelay(t)
127 url := "ws://" + addr
128
129 signer := p8k.MustNew()
130 if err := signer.Generate(); err != nil {
131 t.Fatal(err)
132 }
133
134 // Client 1: subscribe first.
135 c1, err := ws.Connect(url)
136 if err != nil {
137 t.Fatal(err)
138 }
139 defer c1.Close()
140
141 f := &filter.F{
142 Kinds: kind.NewS(kind.New(uint16(1))),
143 Authors: tag.NewFromBytesSlice(signer.Pub()),
144 }
145 limit := uint(10)
146 f.Limit = &limit
147 subscription, err := c1.Subscribe(f)
148 if err != nil {
149 t.Fatal(err)
150 }
151
152 // Wait for EOSE (subscription active, no stored events yet).
153 select {
154 case <-subscription.EOSE:
155 case <-time.After(3 * time.Second):
156 t.Fatal("timeout waiting for EOSE")
157 }
158
159 // Client 2: publish after subscription is active.
160 c2, err := ws.Connect(url)
161 if err != nil {
162 t.Fatal(err)
163 }
164 defer c2.Close()
165
166 ev := &event.E{
167 CreatedAt: time.Now().Unix(),
168 Kind: 1,
169 Content: []byte("real-time!"),
170 }
171 if err := ev.Sign(signer); err != nil {
172 t.Fatal(err)
173 }
174 if err := c2.Publish(ev); err != nil {
175 t.Fatal(err)
176 }
177
178 // Client 1 should receive the event via broadcast.
179 select {
180 case got := <-subscription.Events:
181 if got == nil {
182 t.Fatal("got nil event")
183 }
184 if string(got.Content) != "real-time!" {
185 t.Fatalf("wrong content: %s", got.Content)
186 }
187 case <-time.After(3 * time.Second):
188 t.Fatal("timeout waiting for real-time event")
189 }
190 }
191
192 func TestNIP11Info(t *testing.T) {
193 addr := startRelay(t)
194
195 req, _ := http.NewRequest("GET", "http://"+addr, nil)
196 req.Header.Set("Accept", "application/nostr+json")
197 resp, err := http.DefaultClient.Do(req)
198 if err != nil {
199 t.Fatalf("NIP-11 request: %v", err)
200 }
201 defer resp.Body.Close()
202
203 if resp.Header.Get("Content-Type") != "application/nostr+json" {
204 t.Fatalf("wrong content-type: %s", resp.Header.Get("Content-Type"))
205 }
206 buf := []byte{:1024}
207 n, _ := resp.Body.Read(buf)
208 body := string(buf[:n])
209 if len(body) == 0 || body[0] != '{' {
210 t.Fatalf("bad NIP-11 body: %s", body)
211 }
212 }
213