package controller import ( "net/http/httptest" "strings" "testing" "time" ws "github.com/gorilla/websocket" "github.com/mhsanaei/3x-ui/v3/internal/web/service" ) // V2.0.2: /api/ws frame cadence follows the operator's fast-report // interval (probe_fast_interval) instead of the hub's fixed 2 s tick. // A mid-stream settings change re-paces a live socket: the timer // already in flight still fires on the old cadence, the next one is // armed from the re-read setting. func TestProbeWebSocketCadenceFollowsFastInterval(t *testing.T) { rig := newProbeTestRig(t) _, _ = rig.seedAgent(t, "CadenceNode", true) rig.setSetting(t, "public_page", "on") rig.setSetting(t, "probe_fast_interval", "1") service.InvalidateProbeSnapshot() srv := httptest.NewServer(rig.engine) defer srv.Close() wsURL := "ws" + strings.TrimPrefix(srv.URL, "http") + "/api/ws" conn, _, err := ws.DefaultDialer.Dial(wsURL, nil) if err != nil { t.Fatalf("dial: %v", err) } defer conn.Close() readAt := func() time.Time { t.Helper() _ = conn.SetReadDeadline(time.Now().Add(10 * time.Second)) if _, _, err := conn.ReadMessage(); err != nil { t.Fatalf("ws read: %v", err) } return time.Now() } _ = readAt() // first frame: immediate prev := readAt() for i := 0; i < 2; i++ { now := readAt() if gap := now.Sub(prev); gap < 500*time.Millisecond || gap > 2500*time.Millisecond { t.Fatalf("frame gap at 1 s cadence: %v", gap) } prev = now } // Change to 3 s mid-stream: one in-flight 1 s frame, then the // stream settles onto the new cadence without a reconnect. rig.setSetting(t, "probe_fast_interval", "3") prev = readAt() now := readAt() if gap := now.Sub(prev); gap < 2200*time.Millisecond || gap > 6*time.Second { t.Fatalf("frame gap after change to 3 s: %v", gap) } }