tinyrelayfile
tinyrelay/internal/replication/websocket_transport_test.go/

internal/replication/websocket_transport_test.go raw
1package replication
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "net/http"
8 "testing"
9 "time"
10
11 "github.com/FelineStateMachine/tinyrelay/internal/event"
12)
13
14type silentNegentropyDialer struct{ calls int }
15
16type silentNegentropySocket struct{ responses chan []byte }
17
18func (d *silentNegentropyDialer) Dial(_ context.Context, _ string) (Socket, *http.Response, error) {
19 d.calls++
20 return &silentNegentropySocket{responses: make(chan []byte, 1)}, nil, nil
21}
22
23func (s *silentNegentropySocket) Read(ctx context.Context) ([]byte, error) {
24 select {
25 case response := <-s.responses:
26 return response, nil
27 case <-ctx.Done():
28 return nil, ctx.Err()
29 }
30}
31
32func (s *silentNegentropySocket) Write(_ context.Context, data []byte) error {
33 var message []json.RawMessage
34 if err := json.Unmarshal(data, &message); err != nil || len(message) < 2 {
35 return nil
36 }
37 var kind, id string
38 _ = json.Unmarshal(message[0], &kind)
39 _ = json.Unmarshal(message[1], &id)
40 if kind != "REQ" {
41 return nil // legacy peer silently ignores NEG-OPEN
42 }
43 response, _ := json.Marshal([]any{"EOSE", id})
44 s.responses <- response
45 return nil
46}
47
48func (s *silentNegentropySocket) Close(error) error { return nil }
49
50func TestQuerySynchronizedFallsBackAfterSilentLegacyNegentropyPeer(t *testing.T) {
51 dialer := &silentNegentropyDialer{}
52 transport := &NostrTransport{Dialer: dialer, Timeout: 10 * time.Millisecond}
53 items, err := transport.QuerySynchronized(context.Background(), "ws://relay.example", event.Filter{}, nil)
54 if err != nil {
55 t.Fatal(err)
56 }
57 if len(items) != 0 {
58 t.Fatalf("fallback returned %d events", len(items))
59 }
60 if dialer.calls != 2 {
61 t.Fatalf("transport dials = %d, want negentropy probe plus REQ fallback", dialer.calls)
62 }
63}
64
65func TestQuerySynchronizedCachesSilentLegacyPeerPerTransport(t *testing.T) {
66 dialer := &silentNegentropyDialer{}
67 cache := &LegacyCache{}
68 for i := 0; i < 2; i++ {
69 transport := &NostrTransport{Dialer: dialer, Timeout: 10 * time.Millisecond, LegacyCache: cache}
70 if _, err := transport.QuerySynchronized(context.Background(), "ws://legacy.example", event.Filter{}, nil); err != nil {
71 t.Fatal(err)
72 }
73 }
74 if dialer.calls != 3 {
75 t.Fatalf("transport dials = %d, want one negentropy probe and two REQ pulls", dialer.calls)
76 }
77}
78
79func TestQueryNegentropyIDsReportsMissingRequestedEvents(t *testing.T) {
80 dialer := &batchQueryDialer{events: map[string]event.Event{}}
81 transport := &NostrTransport{Dialer: dialer, Timeout: time.Second}
82 items, err := transport.queryNegentropyIDs(context.Background(), "ws://relay.example", event.Filter{}, []string{"missing"})
83 if err != ErrPullIncomplete {
84 t.Fatalf("error = %v, want ErrPullIncomplete", err)
85 }
86 if len(items) != 0 {
87 t.Fatalf("returned %d events for missing ID", len(items))
88 }
89}
90
91func TestLegacyCacheExpiresAndBoundsEntries(t *testing.T) {
92 cache := &LegacyCache{entries: map[string]time.Time{
93 "ws://expired.example": time.Now().Add(-legacyProbeTTL - time.Second),
94 }}
95 if cache.known("ws://expired.example") {
96 t.Fatal("expired legacy probe was reused")
97 }
98 for i := 0; i < 65; i++ {
99 cache.remember(fmt.Sprintf("ws://relay-%d.example", i))
100 }
101 cache.mu.Lock()
102 defer cache.mu.Unlock()
103 if len(cache.entries) != 64 {
104 t.Fatalf("cache entries = %d, want 64", len(cache.entries))
105 }
106}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs