multica/server/internal/realtime/hub_test.go
Jiayuan Zhang 6dfc61fa86 test: add comprehensive test suite (Go unit/integration, Vitest, Playwright E2E)
- Add JWT middleware unit tests (8 tests covering all auth edge cases)
- Add WebSocket hub tests (5 tests for client lifecycle and broadcast)
- Add full HTTP integration tests (12 tests through real Chi router with DB)
- Add frontend component tests for login, issues, and issue detail pages
- Add auth context unit tests (9 tests for login/logout/name resolution)
- Add Playwright E2E tests for auth, issues, comments, and navigation
- Configure Vitest with jsdom, React plugin, and path aliases

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 11:50:25 +08:00

177 lines
3.9 KiB
Go

package realtime
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/gorilla/websocket"
)
func newTestHub(t *testing.T) (*Hub, *httptest.Server) {
t.Helper()
hub := NewHub()
go hub.Run()
mux := http.NewServeMux()
mux.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
HandleWebSocket(hub, w, r)
})
server := httptest.NewServer(mux)
return hub, server
}
func connectWS(t *testing.T, server *httptest.Server) *websocket.Conn {
t.Helper()
wsURL := "ws" + strings.TrimPrefix(server.URL, "http") + "/ws"
conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
if err != nil {
t.Fatalf("failed to connect WebSocket: %v", err)
}
return conn
}
func TestHub_ClientRegistration(t *testing.T) {
hub, server := newTestHub(t)
defer server.Close()
conn := connectWS(t, server)
defer conn.Close()
time.Sleep(50 * time.Millisecond)
hub.mu.RLock()
count := len(hub.clients)
hub.mu.RUnlock()
if count != 1 {
t.Fatalf("expected 1 client, got %d", count)
}
}
func TestHub_Broadcast(t *testing.T) {
hub, server := newTestHub(t)
defer server.Close()
conn1 := connectWS(t, server)
defer conn1.Close()
conn2 := connectWS(t, server)
defer conn2.Close()
time.Sleep(50 * time.Millisecond)
msg := []byte(`{"type":"issue:created","data":"test"}`)
hub.Broadcast(msg)
conn1.SetReadDeadline(time.Now().Add(2 * time.Second))
_, received1, err := conn1.ReadMessage()
if err != nil {
t.Fatalf("client 1 read error: %v", err)
}
if string(received1) != string(msg) {
t.Fatalf("client 1: expected %s, got %s", msg, received1)
}
conn2.SetReadDeadline(time.Now().Add(2 * time.Second))
_, received2, err := conn2.ReadMessage()
if err != nil {
t.Fatalf("client 2 read error: %v", err)
}
if string(received2) != string(msg) {
t.Fatalf("client 2: expected %s, got %s", msg, received2)
}
}
func TestHub_ClientDisconnect(t *testing.T) {
hub, server := newTestHub(t)
defer server.Close()
conn := connectWS(t, server)
time.Sleep(50 * time.Millisecond)
hub.mu.RLock()
countBefore := len(hub.clients)
hub.mu.RUnlock()
if countBefore != 1 {
t.Fatalf("expected 1 client before disconnect, got %d", countBefore)
}
conn.Close()
time.Sleep(100 * time.Millisecond)
hub.mu.RLock()
countAfter := len(hub.clients)
hub.mu.RUnlock()
if countAfter != 0 {
t.Fatalf("expected 0 clients after disconnect, got %d", countAfter)
}
}
func TestHub_BroadcastToMultipleClients(t *testing.T) {
hub, server := newTestHub(t)
defer server.Close()
const numClients = 5
conns := make([]*websocket.Conn, numClients)
for i := 0; i < numClients; i++ {
conns[i] = connectWS(t, server)
defer conns[i].Close()
}
time.Sleep(50 * time.Millisecond)
hub.mu.RLock()
count := len(hub.clients)
hub.mu.RUnlock()
if count != numClients {
t.Fatalf("expected %d clients, got %d", numClients, count)
}
msg := []byte(`{"type":"test","count":5}`)
hub.Broadcast(msg)
for i, conn := range conns {
conn.SetReadDeadline(time.Now().Add(2 * time.Second))
_, received, err := conn.ReadMessage()
if err != nil {
t.Fatalf("client %d read error: %v", i, err)
}
if string(received) != string(msg) {
t.Fatalf("client %d: expected %s, got %s", i, msg, received)
}
}
}
func TestHub_MultipleBroadcasts(t *testing.T) {
hub, server := newTestHub(t)
defer server.Close()
conn := connectWS(t, server)
defer conn.Close()
time.Sleep(50 * time.Millisecond)
messages := []string{
`{"type":"issue:created"}`,
`{"type":"issue:updated"}`,
`{"type":"issue:deleted"}`,
}
for _, msg := range messages {
hub.Broadcast([]byte(msg))
}
for i, expected := range messages {
conn.SetReadDeadline(time.Now().Add(2 * time.Second))
_, received, err := conn.ReadMessage()
if err != nil {
t.Fatalf("message %d read error: %v", i, err)
}
if string(received) != expected {
t.Fatalf("message %d: expected %s, got %s", i, expected, received)
}
}
}