from __future__ import annotations import pytest from fastapi.testclient import TestClient @pytest.fixture def app(): from cerbero_mcp.server import build_app return build_app( testnet_token="tk_test", mainnet_token="tk_live", title="Test", version="2.0.0", ) def test_apidocs_served(app): c = TestClient(app) r = c.get("/apidocs") assert r.status_code == 200 assert "swagger" in r.text.lower() def test_openapi_json_served(app): c = TestClient(app) r = c.get("/openapi.json") assert r.status_code == 200 spec = r.json() assert spec["info"]["title"] == "Test" # securityScheme BearerAuth presente assert "BearerAuth" in spec["components"]["securitySchemes"] assert spec["components"]["securitySchemes"]["BearerAuth"]["scheme"] == "bearer" def test_redoc_disabled(app): c = TestClient(app) r = c.get("/redoc") assert r.status_code == 404 def test_default_docs_path_disabled(app): c = TestClient(app) r = c.get("/docs") assert r.status_code == 404 def test_health_endpoint(app): c = TestClient(app) r = c.get("/health") assert r.status_code == 200 j = r.json() assert j["status"] == "healthy" assert j["version"] == "2.0.0" assert "uptime_seconds" in j assert "data_timestamp" in j def test_x_duration_ms_header(app): c = TestClient(app) r = c.get("/health") assert "X-Duration-Ms" in r.headers def test_health_ready_empty_registry(app): """Senza registry il readiness ritorna not_ready ma HTTP 200.""" c = TestClient(app) r = c.get("/health/ready") assert r.status_code == 200 j = r.json() assert j["status"] == "not_ready" assert j["clients"] == [] assert j["version"] == "2.0.0" def test_health_ready_all_healthy(app): """Registry con stub client healthy → status=ready.""" from cerbero_mcp.client_registry import ClientRegistry class _StubOk: async def health(self): return {"status": "ok"} async def _builder(exchange, env): # pragma: no cover - non chiamato return _StubOk() reg = ClientRegistry(builder=_builder) reg._clients[("deribit", "testnet")] = _StubOk() reg._clients[("bybit", "mainnet")] = _StubOk() app.state.registry = reg c = TestClient(app) r = c.get("/health/ready") assert r.status_code == 200 j = r.json() assert j["status"] == "ready" assert len(j["clients"]) == 2 for entry in j["clients"]: assert entry["healthy"] is True assert "duration_ms" in entry def test_health_ready_degraded_on_error(app): """Registry con almeno un client che fa raise → status=degraded.""" from cerbero_mcp.client_registry import ClientRegistry class _StubOk: async def health(self): return {"status": "ok"} class _StubFail: async def health(self): raise RuntimeError("boom") async def _builder(exchange, env): # pragma: no cover - non chiamato return _StubOk() reg = ClientRegistry(builder=_builder) reg._clients[("deribit", "testnet")] = _StubOk() reg._clients[("bybit", "mainnet")] = _StubFail() app.state.registry = reg c = TestClient(app) r = c.get("/health/ready") assert r.status_code == 200 j = r.json() assert j["status"] == "degraded" fail = next(c for c in j["clients"] if c["exchange"] == "bybit") assert fail["healthy"] is False assert "RuntimeError" in fail["error"] def test_health_ready_503_when_fail_on_degraded(app, monkeypatch): """READY_FAILS_ON_DEGRADED=true → HTTP 503 quando degraded.""" from cerbero_mcp.client_registry import ClientRegistry class _StubFail: async def health(self): raise RuntimeError("boom") async def _builder(exchange, env): # pragma: no cover - non chiamato return _StubFail() reg = ClientRegistry(builder=_builder) reg._clients[("deribit", "testnet")] = _StubFail() app.state.registry = reg monkeypatch.setenv("READY_FAILS_ON_DEGRADED", "true") c = TestClient(app) r = c.get("/health/ready") assert r.status_code == 503 assert r.json()["status"] == "degraded" def test_health_ready_no_probe_method(app): """Client senza health/is_testnet → marcato healthy con note.""" from cerbero_mcp.client_registry import ClientRegistry class _StubBare: pass async def _builder(exchange, env): # pragma: no cover - non chiamato return _StubBare() reg = ClientRegistry(builder=_builder) reg._clients[("foo", "testnet")] = _StubBare() app.state.registry = reg c = TestClient(app) r = c.get("/health/ready") assert r.status_code == 200 j = r.json() assert j["status"] == "ready" assert j["clients"][0]["note"] == "no probe method" def test_health_ready_in_whitelist_no_auth(app): """/health/ready non richiede bearer.""" c = TestClient(app) # Nessun Authorization header → 200 (whitelist) r = c.get("/health/ready") assert r.status_code == 200