diff --git a/openhands-agent-server/openhands/agent_server/server_details_router.py b/openhands-agent-server/openhands/agent_server/server_details_router.py index 78a3ebcba1..af9ccb370d 100644 --- a/openhands-agent-server/openhands/agent_server/server_details_router.py +++ b/openhands-agent-server/openhands/agent_server/server_details_router.py @@ -1,3 +1,4 @@ +import asyncio import os import sys import time @@ -10,7 +11,7 @@ server_details_router = APIRouter(prefix="", tags=["Server Details"]) _start_time = time.time() _last_event_time = time.time() -_initialization_complete = False +_initialization_complete = asyncio.Event() def _package_version(dist_name: str) -> str: @@ -60,8 +61,7 @@ def mark_initialization_complete() -> None: have finished initializing. Until this is called, the /ready endpoint will return 503 Service Unavailable. """ - global _initialization_complete - _initialization_complete = True + _initialization_complete.set() @server_details_router.get("/alive") @@ -83,7 +83,7 @@ async def ready(response: Response) -> dict[str, str]: This endpoint should be used by Kubernetes readiness probes to determine when the pod is ready to receive traffic. Returns 503 during initialization. """ - if _initialization_complete: + if _initialization_complete.is_set(): return {"status": "ready"} else: response.status_code = 503 diff --git a/tests/agent_server/test_server_details_router.py b/tests/agent_server/test_server_details_router.py new file mode 100644 index 0000000000..fec4363fa6 --- /dev/null +++ b/tests/agent_server/test_server_details_router.py @@ -0,0 +1,50 @@ +"""Tests for the server details router, including the /ready endpoint.""" + +import asyncio + +import pytest +from fastapi.testclient import TestClient + +import openhands.agent_server.server_details_router as sdr +from openhands.agent_server.api import create_app +from openhands.agent_server.config import Config + + +@pytest.fixture(autouse=True) +def reset_initialization_state(): + """Reset the asyncio.Event between tests to avoid state leakage.""" + sdr._initialization_complete = asyncio.Event() + yield + sdr._initialization_complete = asyncio.Event() + + +@pytest.fixture +def client(): + app = create_app(Config(static_files_path=None)) + return TestClient(app) + + +def test_ready_returns_503_before_init(client): + """The /ready endpoint should return 503 while initialization is not complete.""" + response = client.get("/ready") + assert response.status_code == 503 + assert response.json()["status"] == "initializing" + + +def test_ready_returns_200_after_init(client): + """The /ready endpoint should return 200 after mark_initialization_complete().""" + sdr.mark_initialization_complete() + response = client.get("/ready") + assert response.status_code == 200 + assert response.json()["status"] == "ready" + + +def test_ready_resets_after_new_event(client): + """After resetting the event, /ready should return 503 again.""" + sdr.mark_initialization_complete() + assert client.get("/ready").status_code == 200 + + # Simulate a reset (e.g. for testing) + sdr._initialization_complete = asyncio.Event() + response = client.get("/ready") + assert response.status_code == 503