clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

main
tinyrelay / internal / replication / websocket_transport_test.go
websocket_transport_test.go 3.2K
1package replication23import (4 "context"5 "encoding/json"6 "fmt"7 "net/http"8 "testing"9 "time"1011 "github.com/FelineStateMachine/tinyrelay/internal/event"12)1314type silentNegentropyDialer struct{ calls int }1516type silentNegentropySocket struct{ responses chan []byte }1718func (d *silentNegentropyDialer) Dial(_ context.Context, _ string) (Socket, *http.Response, error) {19 d.calls++20 return &silentNegentropySocket{responses: make(chan []byte, 1)}, nil, nil21}2223func (s *silentNegentropySocket) Read(ctx context.Context) ([]byte, error) {24 select {25 case response := <-s.responses:26 return response, nil27 case <-ctx.Done():28 return nil, ctx.Err()29 }30}3132func (s *silentNegentropySocket) Write(_ context.Context, data []byte) error {33 var message []json.RawMessage34 if err := json.Unmarshal(data, &message); err != nil || len(message) < 2 {35 return nil36 }37 var kind, id string38 _ = json.Unmarshal(message[0], &kind)39 _ = json.Unmarshal(message[1], &id)40 if kind != "REQ" {41 return nil // legacy peer silently ignores NEG-OPEN42 }43 response, _ := json.Marshal([]any{"EOSE", id})44 s.responses <- response45 return nil46}4748func (s *silentNegentropySocket) Close(error) error { return nil }4950func 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}6465func 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}7879func 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}9091func 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}