2026-06-11 20:55:57 -04:00
|
|
|
"""Test that start_server configures ws-ping keepalive.
|
|
|
|
|
|
|
|
|
|
The server now uses uvicorn.Server directly (not uvicorn.run) so we stub
|
|
|
|
|
Config + Server + asyncio.run to capture kwargs without starting an event loop.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import asyncio
|
|
|
|
|
import contextlib
|
|
|
|
|
|
2026-06-08 19:50:59 +05:30
|
|
|
import uvicorn
|
|
|
|
|
|
|
|
|
|
from hermes_cli import web_server
|
|
|
|
|
|
|
|
|
|
|
2026-06-11 20:55:57 -04:00
|
|
|
def _stub_uvicorn(monkeypatch):
|
|
|
|
|
"""Replace uvicorn.Config/Server with fakes so start_server returns
|
|
|
|
|
immediately. Returns a dict with captured Config kwargs."""
|
|
|
|
|
captured: dict = {}
|
|
|
|
|
|
|
|
|
|
class _FakeConfig:
|
|
|
|
|
loaded = True
|
|
|
|
|
host = "127.0.0.1"
|
|
|
|
|
port = 8000
|
|
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
|
captured.update(kwargs)
|
|
|
|
|
|
|
|
|
|
def load(self):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
class lifespan_class:
|
|
|
|
|
should_exit = False
|
|
|
|
|
state: dict = {}
|
|
|
|
|
|
|
|
|
|
def __init__(self, *a, **kw):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
async def startup(self):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
async def shutdown(self):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
class _FakeServer:
|
|
|
|
|
should_exit = False
|
|
|
|
|
started = True
|
|
|
|
|
servers: list = []
|
|
|
|
|
lifespan = None
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def capture_signals():
|
|
|
|
|
return contextlib.nullcontext()
|
|
|
|
|
|
|
|
|
|
async def startup(self, sockets=None):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
async def main_loop(self):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
async def shutdown(self, sockets=None):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(uvicorn, "Config", _FakeConfig)
|
|
|
|
|
monkeypatch.setattr(uvicorn, "Server", lambda config: _FakeServer())
|
|
|
|
|
return captured
|
|
|
|
|
|
|
|
|
|
|
2026-06-08 19:50:59 +05:30
|
|
|
def test_start_server_enables_ws_ping_for_half_open_detection(monkeypatch):
|
|
|
|
|
"""WS ping must be configured so half-open connections (reverse-proxy 524,
|
|
|
|
|
dropped tunnels) raise WebSocketDisconnect into the reaping path (#32377)."""
|
2026-06-11 20:55:57 -04:00
|
|
|
captured = _stub_uvicorn(monkeypatch)
|
2026-06-08 19:50:59 +05:30
|
|
|
|
2026-06-11 20:55:57 -04:00
|
|
|
# Loopback bind => no auth gate, so this reaches the Config constructor.
|
2026-06-08 19:50:59 +05:30
|
|
|
web_server.start_server(host="127.0.0.1", port=0, open_browser=False)
|
|
|
|
|
|
|
|
|
|
assert captured["ws_ping_interval"] == 20.0
|
|
|
|
|
assert captured["ws_ping_timeout"] == 20.0
|