fix: stop WS reconnect storm on auth rejection; map 502/503/504 to NetworkError

WebSocket endpoints now accept then close with real codes (4001 auth, 4003
permission) so clients detect auth failure via event.code instead of an opaque
403 handshake, ending the infinite reconnect storm. _authenticate_websocket
logs the actual JWT/API-key failure reason. Frontend WS consumers stop on
auth rejection and use capped exponential backoff for transient failures.

async_network.request() routes proxy 502/503/504 (HTML) responses to
NetworkError so migration/maintenance surface a clean 503 instead of a
500 JSON-parse traceback.
This commit is contained in:
2026-08-01 13:23:30 +07:00
parent 46457b4191
commit a1cb18fad9
16 changed files with 307 additions and 34 deletions

View File

@@ -60,11 +60,13 @@ class TestWebSocketEndpointFull:
@pytest.mark.asyncio
async def test_auth_rejected_closes(self):
from src.app import websocket_endpoint
ws = MagicMock(); ws.query_params = {}; ws.close = AsyncMock()
ws = MagicMock(); ws.query_params = {}; ws.close = AsyncMock(); ws.accept = AsyncMock()
with patch("src.app._authenticate_websocket", return_value=False):
await websocket_endpoint(ws, "task-1")
# Auth rejection accepts the handshake then closes with 4001 so the client can
# read event.code instead of an opaque HTTP 403 handshake rejection.
ws.accept.assert_called_once()
ws.close.assert_called_once_with(code=4001, reason="Authentication required")
ws.accept.assert_not_called()
# #endregion Test.AppModule.TestAuthRejectedCloses
# #region Test.AppModule.TestAcceptsAndSendsInitialStatus [C:2] [TYPE Function]