test(hub): add room-scoped broadcast isolation tests (P03)
Add tests verifying that the broadcast case in Hub.Run() correctly
sends messages only to clients in the same room as the sender:
- TestIntegration_RoomIsolation_MessagesOnlyGoToSameRoom: verifies
messages from room-a are received by room-a clients and NOT by
room-b clients
- TestIntegration_RoomIsolation_MultipleRoomsIndependent: verifies
two rooms operate independently with no message leakage
- TestIntegration_BroadcastToEmptyRoom: verifies graceful handling
when broadcasting to a non-existent room (no panic, hub remains
functional)
- TestBroadcastRoomIsolation: unit-level room isolation test using
the broadcast channel directly
Also adds dialWSWithRoom helper for room-aware WebSocket connections
in integration tests.
🤖 Assisted by the code-assist SOP
This commit is contained in:
parent
8eaba398dc
commit
48d47dfc92
@ -24,7 +24,7 @@ func setupTestServer(t *testing.T) (*httptest.Server, *Hub) {
|
||||
return server, h
|
||||
}
|
||||
|
||||
// helper: dial a WebSocket connection to the test server
|
||||
// helper: dial a WebSocket connection to the test server (default room)
|
||||
func dialWS(t *testing.T, server *httptest.Server) *websocket.Conn {
|
||||
t.Helper()
|
||||
wsURL := "ws" + strings.TrimPrefix(server.URL, "http")
|
||||
@ -35,6 +35,17 @@ func dialWS(t *testing.T, server *httptest.Server) *websocket.Conn {
|
||||
return conn
|
||||
}
|
||||
|
||||
// helper: dial a WebSocket connection to a specific room
|
||||
func dialWSWithRoom(t *testing.T, server *httptest.Server, room string) *websocket.Conn {
|
||||
t.Helper()
|
||||
wsURL := "ws" + strings.TrimPrefix(server.URL, "http") + "?room=" + room
|
||||
conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to dial WebSocket (room=%q): %v", room, err)
|
||||
}
|
||||
return conn
|
||||
}
|
||||
|
||||
// helper: wait until hub reaches expected client count or timeout
|
||||
func waitForClients(t *testing.T, h *Hub, expected int, timeout time.Duration) {
|
||||
t.Helper()
|
||||
@ -356,3 +367,137 @@ func TestIntegration_LargeMessage(t *testing.T) {
|
||||
t.Errorf("Expected message length %d, got %d", 64*1024, len(msg))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegration_RoomIsolation_MessagesOnlyGoToSameRoom(t *testing.T) {
|
||||
server, h := setupTestServer(t)
|
||||
defer server.Close()
|
||||
defer h.Shutdown()
|
||||
|
||||
// Connect 2 clients to room-a, 1 client to room-b
|
||||
connA1 := dialWSWithRoom(t, server, "room-a")
|
||||
defer connA1.Close()
|
||||
connA2 := dialWSWithRoom(t, server, "room-a")
|
||||
defer connA2.Close()
|
||||
connB1 := dialWSWithRoom(t, server, "room-b")
|
||||
defer connB1.Close()
|
||||
|
||||
waitForClients(t, h, 3, time.Second)
|
||||
|
||||
// Send a message from client A1
|
||||
testMsg := "hello room-a"
|
||||
if err := connA1.WriteMessage(websocket.TextMessage, []byte(testMsg)); err != nil {
|
||||
t.Fatalf("Failed to send message: %v", err)
|
||||
}
|
||||
|
||||
// Both room-a clients should receive the message
|
||||
for i, conn := range []*websocket.Conn{connA1, connA2} {
|
||||
conn.SetReadDeadline(time.Now().Add(time.Second))
|
||||
_, msg, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
t.Fatalf("Room-a client %d failed to read message: %v", i+1, err)
|
||||
}
|
||||
if string(msg) != testMsg {
|
||||
t.Errorf("Room-a client %d expected %q, got %q", i+1, testMsg, string(msg))
|
||||
}
|
||||
}
|
||||
|
||||
// Room-b client should NOT receive the message
|
||||
connB1.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
|
||||
_, _, err := connB1.ReadMessage()
|
||||
if err == nil {
|
||||
t.Fatal("Room-b client should NOT have received a message from room-a")
|
||||
}
|
||||
// Timeout error is expected — message was correctly not delivered
|
||||
}
|
||||
|
||||
func TestIntegration_RoomIsolation_MultipleRoomsIndependent(t *testing.T) {
|
||||
server, h := setupTestServer(t)
|
||||
defer server.Close()
|
||||
defer h.Shutdown()
|
||||
|
||||
// Connect clients to two different rooms
|
||||
connA := dialWSWithRoom(t, server, "alpha")
|
||||
defer connA.Close()
|
||||
connB := dialWSWithRoom(t, server, "beta")
|
||||
defer connB.Close()
|
||||
|
||||
waitForClients(t, h, 2, time.Second)
|
||||
|
||||
// Send message from room alpha
|
||||
msgAlpha := "alpha message"
|
||||
if err := connA.WriteMessage(websocket.TextMessage, []byte(msgAlpha)); err != nil {
|
||||
t.Fatalf("Failed to send alpha message: %v", err)
|
||||
}
|
||||
|
||||
// Room alpha client receives its own message
|
||||
connA.SetReadDeadline(time.Now().Add(time.Second))
|
||||
_, msg, err := connA.ReadMessage()
|
||||
if err != nil {
|
||||
t.Fatalf("Alpha client failed to read: %v", err)
|
||||
}
|
||||
if string(msg) != msgAlpha {
|
||||
t.Errorf("Alpha client expected %q, got %q", msgAlpha, string(msg))
|
||||
}
|
||||
|
||||
// Send message from room beta
|
||||
msgBeta := "beta message"
|
||||
if err := connB.WriteMessage(websocket.TextMessage, []byte(msgBeta)); err != nil {
|
||||
t.Fatalf("Failed to send beta message: %v", err)
|
||||
}
|
||||
|
||||
// Room beta client receives its own message
|
||||
connB.SetReadDeadline(time.Now().Add(time.Second))
|
||||
_, msg, err = connB.ReadMessage()
|
||||
if err != nil {
|
||||
t.Fatalf("Beta client failed to read: %v", err)
|
||||
}
|
||||
if string(msg) != msgBeta {
|
||||
t.Errorf("Beta client expected %q, got %q", msgBeta, string(msg))
|
||||
}
|
||||
|
||||
// Verify alpha did NOT receive beta's message
|
||||
connA.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
|
||||
_, _, err = connA.ReadMessage()
|
||||
if err == nil {
|
||||
t.Fatal("Alpha client should NOT have received beta's message")
|
||||
}
|
||||
|
||||
// Verify beta did NOT receive alpha's message
|
||||
connB.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
|
||||
_, _, err = connB.ReadMessage()
|
||||
if err == nil {
|
||||
t.Fatal("Beta client should NOT have received alpha's message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegration_BroadcastToEmptyRoom(t *testing.T) {
|
||||
server, h := setupTestServer(t)
|
||||
defer server.Close()
|
||||
defer h.Shutdown()
|
||||
|
||||
// Connect a client to room-a and a separate client to verify hub is functional
|
||||
connA := dialWSWithRoom(t, server, "room-a")
|
||||
defer connA.Close()
|
||||
|
||||
waitForClients(t, h, 1, time.Second)
|
||||
|
||||
// Send directly to broadcast channel targeting a non-existent room.
|
||||
// This should be handled gracefully (no panic, no delivery).
|
||||
h.broadcast <- broadcastMsg{room: "non-existent", data: []byte("ghost message")}
|
||||
|
||||
// Give the hub time to process
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
// Now send a real message to room-a to confirm hub is still functional
|
||||
h.broadcast <- broadcastMsg{room: "room-a", data: []byte("real message")}
|
||||
|
||||
connA.SetReadDeadline(time.Now().Add(time.Second))
|
||||
_, msg, err := connA.ReadMessage()
|
||||
if err != nil {
|
||||
t.Fatalf("Room-a client failed to read real message after empty-room broadcast: %v", err)
|
||||
}
|
||||
if string(msg) != "real message" {
|
||||
t.Errorf("Expected %q, got %q", "real message", string(msg))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -245,3 +245,38 @@ func TestUnregisterUnknownConnNoPanic(t *testing.T) {
|
||||
t.Errorf("Expected 0 clients, got %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBroadcastRoomIsolation(t *testing.T) {
|
||||
h := New(newTestLogger())
|
||||
go h.Run()
|
||||
defer h.Shutdown()
|
||||
|
||||
connA := dialTestHub(t, h, "room-a")
|
||||
defer connA.Close()
|
||||
connB := dialTestHub(t, h, "room-b")
|
||||
defer connB.Close()
|
||||
|
||||
// Allow registers to be processed
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
// Send message to room-a via broadcast channel
|
||||
h.broadcast <- broadcastMsg{room: "room-a", data: []byte("for-a-only")}
|
||||
|
||||
// Room-a client should receive the message
|
||||
connA.SetReadDeadline(time.Now().Add(time.Second))
|
||||
_, msg, err := connA.ReadMessage()
|
||||
if err != nil {
|
||||
t.Fatalf("Room-a client failed to read: %v", err)
|
||||
}
|
||||
if string(msg) != "for-a-only" {
|
||||
t.Errorf("Expected %q, got %q", "for-a-only", string(msg))
|
||||
}
|
||||
|
||||
// Room-b client should NOT receive the message (short timeout)
|
||||
connB.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
|
||||
_, _, err = connB.ReadMessage()
|
||||
if err == nil {
|
||||
t.Fatal("Room-b client should NOT have received room-a's message")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user