Skip to content

Commit 5d88a02

Browse files
committed
.
1 parent a52a196 commit 5d88a02

2 files changed

Lines changed: 139 additions & 3 deletions

File tree

code-interpreter/app/models/schemas.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,6 @@ class ListFilesResponse(BaseModel):
117117
)
118118

119119

120-
# ── Health check models ──────────────────────────────────────────────
121-
122-
123120
class HealthResponse(BaseModel):
124121
status: Literal["ok", "error"]
125122
message: StrictStr | None = None
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
from __future__ import annotations
2+
3+
import subprocess
4+
from collections.abc import Generator
5+
from unittest.mock import patch
6+
7+
import pytest
8+
from fastapi.testclient import TestClient
9+
10+
from app.main import create_app
11+
from app.services.executor_base import HealthCheck
12+
from app.services.executor_docker import DockerExecutor
13+
from app.services.executor_factory import get_executor
14+
15+
16+
@pytest.fixture(autouse=True)
17+
def _clear_executor_cache() -> Generator[None, None, None]:
18+
"""Reset the lru_cache on get_executor so patches take effect."""
19+
get_executor.cache_clear()
20+
yield
21+
get_executor.cache_clear()
22+
23+
24+
def test_health_returns_ok_when_backend_healthy() -> None:
25+
client = TestClient(create_app())
26+
response = client.get("/health")
27+
28+
assert response.status_code == 200
29+
body = response.json()
30+
assert body["status"] == "ok"
31+
assert body["message"] is None
32+
33+
34+
def test_health_returns_error_when_backend_unhealthy() -> None:
35+
unhealthy = HealthCheck(status="error", message="daemon down")
36+
37+
with patch.object(DockerExecutor, "check_health", return_value=unhealthy):
38+
client = TestClient(create_app())
39+
response = client.get("/health")
40+
41+
assert response.status_code == 200
42+
body = response.json()
43+
assert body["status"] == "error"
44+
assert body["message"] == "daemon down"
45+
46+
47+
def _make_completed(returncode: int, stderr: bytes = b"") -> subprocess.CompletedProcess[bytes]:
48+
return subprocess.CompletedProcess(args=[], returncode=returncode, stdout=b"", stderr=stderr)
49+
50+
51+
def test_docker_health_ok() -> None:
52+
"""Both Docker daemon and image check succeed."""
53+
with patch("app.services.executor_docker.subprocess.run", return_value=_make_completed(0)):
54+
executor = DockerExecutor()
55+
result = executor.check_health()
56+
57+
assert result.status == "ok"
58+
assert result.message is None
59+
60+
61+
def test_docker_health_daemon_unreachable() -> None:
62+
"""Docker daemon returns non-zero exit code."""
63+
with patch(
64+
"app.services.executor_docker.subprocess.run",
65+
return_value=_make_completed(1, stderr=b"Cannot connect to the Docker daemon"),
66+
):
67+
executor = DockerExecutor()
68+
result = executor.check_health()
69+
70+
assert result.status == "error"
71+
assert "Docker daemon not reachable" in (result.message or "")
72+
73+
74+
def test_docker_health_daemon_timeout() -> None:
75+
"""Docker daemon command times out."""
76+
with patch(
77+
"app.services.executor_docker.subprocess.run",
78+
side_effect=subprocess.TimeoutExpired(cmd="docker", timeout=5),
79+
):
80+
executor = DockerExecutor()
81+
result = executor.check_health()
82+
83+
assert result.status == "error"
84+
assert "not responding" in (result.message or "")
85+
86+
87+
def test_docker_health_binary_not_found() -> None:
88+
"""Docker binary does not exist."""
89+
with patch(
90+
"app.services.executor_docker.subprocess.run",
91+
side_effect=FileNotFoundError,
92+
):
93+
executor = DockerExecutor()
94+
result = executor.check_health()
95+
96+
assert result.status == "error"
97+
assert "not found" in (result.message or "")
98+
99+
100+
def test_docker_health_image_missing() -> None:
101+
"""Docker daemon is reachable but the executor image is not available."""
102+
daemon_ok = _make_completed(0)
103+
image_missing = _make_completed(1)
104+
105+
call_count = 0
106+
107+
def _side_effect(*args: object, **kwargs: object) -> subprocess.CompletedProcess[bytes]:
108+
nonlocal call_count
109+
call_count += 1
110+
# First call: docker version (daemon check) → ok
111+
# Second call: docker image inspect → fail
112+
return daemon_ok if call_count == 1 else image_missing
113+
114+
with patch("app.services.executor_docker.subprocess.run", side_effect=_side_effect):
115+
executor = DockerExecutor()
116+
result = executor.check_health()
117+
118+
assert result.status == "error"
119+
assert "not available locally" in (result.message or "")
120+
121+
122+
def test_docker_health_image_check_timeout() -> None:
123+
"""Docker daemon is reachable but the image inspect times out."""
124+
daemon_ok = _make_completed(0)
125+
call_count = 0
126+
127+
def _side_effect(*args: object, **kwargs: object) -> subprocess.CompletedProcess[bytes]:
128+
nonlocal call_count
129+
call_count += 1
130+
if call_count == 1:
131+
return daemon_ok
132+
raise subprocess.TimeoutExpired(cmd="docker", timeout=5)
133+
134+
with patch("app.services.executor_docker.subprocess.run", side_effect=_side_effect):
135+
executor = DockerExecutor()
136+
result = executor.check_health()
137+
138+
assert result.status == "error"
139+
assert "Timeout checking image" in (result.message or "")

0 commit comments

Comments
 (0)