- frontend: fix auto-start on dashboards->/agent navigation (undefined params ReferenceError), route initial connect through ConnectionManager with auto-retry, reset runModel on objectId change and failed recovery - agent: fix closure-over-loop-variable bug in _inject_env_id_into_tools (env now resolved from request-local ContextVar; idempotent wrapping), make execute_dashboard_result.result_key optional, resilient checkpoint resume with ToolMessage repair + direct-tool fallback, remove dead fast-path, consolidate tool_call parsing in _tool_resolver, context-safe ContextVar resets - backend: llm-config gated by strict service-only auth (no user-JWT fallback), tighten idempotent run reuse (dashboard/env/intent match + 6h staleness), terminal event transitions run.status to COMPLETED/FAILED/CANCELLED, null-safe metric parsing in dashboard query model - run.sh/docker-compose: require SERVICE_JWT (random per-run secret) instead of public default
95 lines
3.9 KiB
Python
95 lines
3.9 KiB
Python
# #region Test.AppModule.Handlers [C:3] [TYPE Module] [SEMANTICS test,app,handlers,exceptions]
|
|
# @BRIEF Tests for app.py — global_exception_handler, network_error_handler.
|
|
# @RELATION BINDS_TO -> [App.AppModule]
|
|
# @TEST_EDGE: unhandled_exception -> returns 500 JSON with path
|
|
# @TEST_EDGE: network_error -> returns 503
|
|
# @TEST_INVARIANT: every_500_logged -> VERIFIED_BY: test_global_exception_handler_logs_and_returns_500
|
|
|
|
from pathlib import Path
|
|
import sys
|
|
import json
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
|
|
|
import pytest
|
|
from unittest.mock import MagicMock
|
|
from fastapi import Request, HTTPException
|
|
from fastapi.responses import JSONResponse
|
|
|
|
|
|
# #region _make_mock_request [C:1] [TYPE Function]
|
|
def _make_mock_request(method="GET", path="/api/test", client_host="127.0.0.1"):
|
|
req = MagicMock(spec=Request)
|
|
req.method = method
|
|
req.url = MagicMock()
|
|
req.url.path = path
|
|
req.client = MagicMock()
|
|
req.client.host = client_host
|
|
req.query_params = {}
|
|
return req
|
|
# #endregion _make_mock_request
|
|
|
|
|
|
class TestExceptionHandlers:
|
|
"""global_exception_handler and network_error_handler."""
|
|
|
|
# #region Test.AppModule.TestGlobalHandler500 [C:2] [TYPE Function]
|
|
@pytest.mark.asyncio
|
|
async def test_global_handler_500(self):
|
|
from src.app import global_exception_handler
|
|
request = _make_mock_request(method="POST", path="/api/break")
|
|
response = await global_exception_handler(request, ValueError("broke"))
|
|
assert isinstance(response, JSONResponse)
|
|
assert response.status_code == 500
|
|
body = json.loads(response.body)
|
|
assert body["detail"] == "Internal server error"
|
|
assert body["path"] == "/api/break"
|
|
# #endregion Test.AppModule.TestGlobalHandler500
|
|
|
|
# #region Test.AppModule.TestGlobalHandlerUnknownClient [C:2] [TYPE Function]
|
|
@pytest.mark.asyncio
|
|
async def test_global_handler_unknown_client(self):
|
|
from src.app import global_exception_handler
|
|
req = _make_mock_request()
|
|
req.client = None
|
|
response = await global_exception_handler(req, RuntimeError("no client"))
|
|
assert response.status_code == 500
|
|
# #endregion Test.AppModule.TestGlobalHandlerUnknownClient
|
|
|
|
# #region Test.AppModule.TestNetworkErrorHandler [C:2] [TYPE Function]
|
|
@pytest.mark.asyncio
|
|
async def test_network_error_handler(self):
|
|
from src.app import network_error_handler
|
|
from src.core.utils.network import NetworkError
|
|
request = _make_mock_request()
|
|
response = await network_error_handler(request, NetworkError("down"))
|
|
from starlette.responses import JSONResponse
|
|
|
|
assert isinstance(response, JSONResponse)
|
|
assert response.status_code == 503
|
|
body = json.loads(response.body)
|
|
assert "Environment unavailable" in body["detail"]
|
|
# #endregion Test.AppModule.TestNetworkErrorHandler
|
|
|
|
# #region Test.AppModule.TestGlobalHandlerWithQueryParams [C:2] [TYPE Function]
|
|
@pytest.mark.asyncio
|
|
async def test_global_handler_with_query_params(self):
|
|
from src.app import global_exception_handler
|
|
req = _make_mock_request()
|
|
req.query_params = {"env_id": "prod"}
|
|
response = await global_exception_handler(req, Exception("test"))
|
|
assert response.status_code == 500
|
|
# #endregion Test.AppModule.TestGlobalHandlerWithQueryParams
|
|
|
|
# #region Test.AppModule.TestInternalServerErrorReturnsJson [C:2] [TYPE Function]
|
|
@pytest.mark.asyncio
|
|
async def test_internal_server_error_returns_json(self):
|
|
from src.app import global_exception_handler
|
|
request = _make_mock_request(method="POST", path="/api/crash")
|
|
response = await global_exception_handler(request, RuntimeError("crash"))
|
|
assert response.status_code == 500
|
|
body = json.loads(response.body)
|
|
assert body["path"] == "/api/crash"
|
|
# #endregion Test.AppModule.TestInternalServerErrorReturnsJson
|
|
# #endregion Test.AppModule.Handlers
|