go-nostr/relay_test.go
alex c327f622f3 relay: introduce ConnectContext for better control over network latency
A websocket dial may hand for an unreasonably long time and a nostr client
has no control over this when trying to connect to a relay.

Go started introducing context in networking since 2014 -
see https://go.dev/blog/context - and by now many net functions have
XxxContext equivalent, such as DialContext.

Example usage of the change introduced by this commit:

    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()
    r, err := nostr.RelayConnectContext(ctx, "ws://relay.example.org")

The code above makes RelayConnectContext last at most 3 sec, returning
an error if a connection cannot be established in the given time.
This helps whenever a tight control over connection latency is required,
such as distributed systems.

The change is backwards-compatible except the case where RelayPool.Add
sent an error over the returned channel without actually closing said
channel. I believe it was a bug.
2022-12-17 22:33:05 -03:00

73 lines
1.7 KiB
Go

package nostr
import (
"context"
"errors"
"io"
"net/http"
"net/http/httptest"
"sync"
"testing"
"time"
"golang.org/x/net/websocket"
)
func TestConnectContext(t *testing.T) {
// fake relay server
var mu sync.Mutex // guards connected to satisfy go test -race
var connected bool
ws := newWebsocketServer(func(conn *websocket.Conn) {
mu.Lock()
connected = true
mu.Unlock()
io.ReadAll(conn) // discard all input
})
defer ws.Close()
// relay client
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
r, err := RelayConnectContext(ctx, ws.URL)
if err != nil {
t.Fatalf("RelayConnectContext: %v", err)
}
defer r.Close()
mu.Lock()
defer mu.Unlock()
if !connected {
t.Error("fake relay server saw no client connect")
}
}
func TestConnectContextCanceled(t *testing.T) {
// fake relay server
ws := newWebsocketServer(func(conn *websocket.Conn) {
io.ReadAll(conn) // discard all input
})
defer ws.Close()
// relay client
ctx, cancel := context.WithCancel(context.Background())
cancel() // make ctx expired
_, err := RelayConnectContext(ctx, ws.URL)
if !errors.Is(err, context.Canceled) {
t.Errorf("RelayConnectContext returned %v error; want context.Canceled", err)
}
}
func newWebsocketServer(handler func(*websocket.Conn)) *httptest.Server {
return httptest.NewServer(&websocket.Server{
Handshake: anyOriginHandshake,
Handler: handler,
})
}
// anyOriginHandshake is an alternative to default in golang.org/x/net/websocket
// which checks for origin. nostr client sends no origin and it makes no difference
// for the tests here anyway.
var anyOriginHandshake = func(conf *websocket.Config, r *http.Request) error {
return nil
}