package ws // system_ws_integration_test.go — MAJ-4 補丁:/ws/system 整合 smoke test // // 啟一個 httptest server 掛 SystemEventsHandler,真的用 gorilla WebSocket client // 連進去,然後呼叫 hub.BroadcastToRoom("system", ...),驗證 client 收到訊息。 // // 這個測試取代外部 websocat / wscat 的需求,讓 smoke test 在 CI 就能跑。 import ( "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "time" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" ) func TestSystemEventsHandler_ReceivesBroadcast(t *testing.T) { gin.SetMode(gin.TestMode) hub := NewHub() go hub.Run() r := gin.New() r.GET("/ws/system", SystemEventsHandler(hub)) srv := httptest.NewServer(r) defer srv.Close() wsURL := "ws" + strings.TrimPrefix(srv.URL, "http") + "/ws/system" dialer := websocket.DefaultDialer conn, _, err := dialer.Dial(wsURL, http.Header{}) if err != nil { t.Fatalf("dial: %v", err) } defer conn.Close() // 給 Hub.register channel 一點時間處理 client 加入 room // (RegisterSync 會同步等完,但我們這邊是透過 HTTP upgrade 流程, // client 先連上後才 RegisterSync — 需要等 handler 執行到那一行) // 改用 poll:持續廣播直到收到或 timeout。 _ = conn.SetReadDeadline(time.Now().Add(2 * time.Second)) // 等 hub 吸收 Register(最多 500 ms) deadline := time.Now().Add(500 * time.Millisecond) for time.Now().Before(deadline) { hub.mu.RLock() n := len(hub.rooms["system"]) hub.mu.RUnlock() if n > 0 { break } time.Sleep(10 * time.Millisecond) } // 廣播 shutdown-imminent hub.BroadcastToRoom("system", map[string]interface{}{ "type": "server:shutdown-imminent", "reason": "quit", "ts": time.Now().UnixMilli(), }) // Read 第一則訊息 _, data, err := conn.ReadMessage() if err != nil { t.Fatalf("read: %v", err) } var got map[string]interface{} if err := json.Unmarshal(data, &got); err != nil { t.Fatalf("json: %v; raw=%s", err, string(data)) } if got["type"] != "server:shutdown-imminent" { t.Errorf("wrong type: %+v", got) } if got["reason"] != "quit" { t.Errorf("wrong reason: %+v", got) } }