package websocket import ( "bufio" "context" "errors" "net" "testing" "time" "git.avsdev.uk/Go/job-search/internal/config" ) // pipeClient runs a plain TCP client of h on an in-memory pipe and reads its // welcome. It returns the peer's end, which the test stops reading from to // make the client's writes block, and a channel closed when ServeTCP returns. func pipeClient(t *testing.T, h *Hub) (net.Conn, chan struct{}) { t.Helper() server, peer := net.Pipe() t.Cleanup(func() { server.Close() peer.Close() }) done := make(chan struct{}) go func() { h.ServeTCP(context.Background(), server) close(done) }() peer.SetReadDeadline(time.Now().Add(time.Second)) if _, err := bufio.NewReader(peer).ReadString('\n'); err != nil { t.Fatalf("welcome: %v", err) } waitCount(t, h, 1) return peer, done } // If ctx expires before the clients have closed, Shutdown closes the rest at // once and returns ctx's error. func TestShutdownTimeout(t *testing.T) { // No write timeout: the client's goodbye blocks until it is cut short. h := NewHub(config.JSCServerWebSocket{}, WithLogger(discardLog)) _, done := pipeClient(t, h) ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond) defer cancel() if err := h.Shutdown(ctx); !errors.Is(err, context.DeadlineExceeded) { t.Fatalf("Shutdown = %v; want context.DeadlineExceeded", err) } select { case <-done: case <-time.After(time.Second): t.Fatal("client not closed after the shutdown timeout") } } // Shutdown with no clients returns at once, even with an expired ctx. func TestShutdownNoClients(t *testing.T) { h := NewHub(config.JSCServerWebSocket{}, WithLogger(discardLog)) ctx, cancel := context.WithCancel(context.Background()) cancel() for range 20 { if err := h.Shutdown(ctx); err != nil { t.Fatalf("Shutdown = %v; want nil", err) } } } // A ping that can't be sent within the pong timeout drops the client. func TestPingFailureDropsClient(t *testing.T) { h := NewHub( config.JSCServerWebSocket{ PingInterval: 10 * time.Millisecond, PongTimeout: 20 * time.Millisecond, }, WithLogger(discardLog), ) _, done := pipeClient(t, h) select { case <-done: case <-time.After(time.Second): t.Fatal("client not dropped after a failed ping") } waitCount(t, h, 0) } func TestMaxMessageSize(t *testing.T) { tests := []struct { conf config.JSCServerWebSocket want int }{ {config.JSCServerWebSocket{}, defaultMaxMessageSize}, {config.JSCServerWebSocket{ MaxMessageSize: -1, }, defaultMaxMessageSize}, {config.JSCServerWebSocket{ MaxMessageSize: 100, }, 100}, } for _, tt := range tests { h := NewHub(tt.conf, WithLogger(discardLog)) if got := h.maxMessageSize(); got != tt.want { t.Errorf( "maxMessageSize(%d) = %d; want %d", tt.conf.MaxMessageSize, got, tt.want, ) } } }