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{}, 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{}, 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, }, 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, discardLog) if got := h.maxMessageSize(); got != tt.want { t.Errorf("maxMessageSize(%d) = %d; want %d", tt.conf.MaxMessageSize, got, tt.want) } } }