Adds three new monitoring endpoints: - GET /health/gitea — Verifies Gitea API connectivity - GET /health/discord — Reports Discord bot connection status - GET /health/queue — Returns pending task count All endpoints return JSON with status field. The Gitea endpoint includes the API status code on success or error message on failure. Discord endpoint returns "not_configured", "connecting", or "ok" with bot username. Queue endpoint includes pending_tasks count. Tests use mock lifespan to avoid initializing task queue, message store, dispatcher, and discord handler during testing.
156 lines
5.4 KiB
Python
156 lines
5.4 KiB
Python
"""Health check 엔드포인트 테스트."""
|
|
import pytest
|
|
from contextlib import asynccontextmanager
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import httpx
|
|
|
|
|
|
@asynccontextmanager
|
|
async def mock_lifespan(app):
|
|
"""테스트용 mock lifespan."""
|
|
yield
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_health_basic():
|
|
"""기본 health check가 200을 반환한다."""
|
|
from agent.webapp import app
|
|
# Override lifespan for testing
|
|
app.router.lifespan_context = mock_lifespan
|
|
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://test") as client:
|
|
resp = await client.get("/health")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["status"] == "ok"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_health_queue():
|
|
"""큐 health check가 pending 카운트를 반환한다."""
|
|
from agent.webapp import app
|
|
app.router.lifespan_context = mock_lifespan
|
|
|
|
mock_queue = MagicMock()
|
|
mock_queue.get_pending = AsyncMock(return_value=[{"id": "1"}, {"id": "2"}])
|
|
|
|
with patch("agent.task_queue.get_task_queue", new_callable=AsyncMock, return_value=mock_queue):
|
|
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://test") as client:
|
|
resp = await client.get("/health/queue")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["status"] == "ok"
|
|
assert data["pending_tasks"] == 2
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_health_queue_empty():
|
|
"""큐가 비어있을 때 0을 반환한다."""
|
|
from agent.webapp import app
|
|
app.router.lifespan_context = mock_lifespan
|
|
|
|
mock_queue = MagicMock()
|
|
mock_queue.get_pending = AsyncMock(return_value=[])
|
|
|
|
with patch("agent.task_queue.get_task_queue", new_callable=AsyncMock, return_value=mock_queue):
|
|
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://test") as client:
|
|
resp = await client.get("/health/queue")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["status"] == "ok"
|
|
assert data["pending_tasks"] == 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_health_discord_not_configured():
|
|
"""Discord가 설정되지 않았을 때."""
|
|
from agent.webapp import app
|
|
app.router.lifespan_context = mock_lifespan
|
|
# Ensure no discord_handler on app.state
|
|
if hasattr(app.state, "discord_handler"):
|
|
delattr(app.state, "discord_handler")
|
|
|
|
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://test") as client:
|
|
resp = await client.get("/health/discord")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["status"] == "not_configured"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_health_discord_ready():
|
|
"""Discord 봇이 준비되었을 때."""
|
|
from agent.webapp import app
|
|
app.router.lifespan_context = mock_lifespan
|
|
|
|
mock_bot = MagicMock()
|
|
mock_bot.is_ready.return_value = True
|
|
mock_bot.user = "TestBot#1234"
|
|
|
|
mock_handler = MagicMock()
|
|
mock_handler.bot = mock_bot
|
|
app.state.discord_handler = mock_handler
|
|
|
|
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://test") as client:
|
|
resp = await client.get("/health/discord")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["status"] == "ok"
|
|
assert data["user"] == "TestBot#1234"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_health_discord_connecting():
|
|
"""Discord 봇이 연결 중일 때."""
|
|
from agent.webapp import app
|
|
app.router.lifespan_context = mock_lifespan
|
|
|
|
mock_bot = MagicMock()
|
|
mock_bot.is_ready.return_value = False
|
|
|
|
mock_handler = MagicMock()
|
|
mock_handler.bot = mock_bot
|
|
app.state.discord_handler = mock_handler
|
|
|
|
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://test") as client:
|
|
resp = await client.get("/health/discord")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["status"] == "connecting"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_health_gitea_ok():
|
|
"""Gitea API 연결이 성공할 때."""
|
|
from agent.webapp import app
|
|
app.router.lifespan_context = mock_lifespan
|
|
|
|
mock_response = MagicMock()
|
|
mock_response.status_code = 200
|
|
|
|
mock_client = MagicMock()
|
|
mock_client._client.get = AsyncMock(return_value=mock_response)
|
|
|
|
with patch("agent.utils.gitea_client.get_gitea_client", return_value=mock_client):
|
|
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://test") as client:
|
|
resp = await client.get("/health/gitea")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["status"] == "ok"
|
|
assert data["gitea_status_code"] == 200
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_health_gitea_error():
|
|
"""Gitea API 연결이 실패할 때."""
|
|
from agent.webapp import app
|
|
app.router.lifespan_context = mock_lifespan
|
|
|
|
with patch("agent.utils.gitea_client.get_gitea_client", side_effect=Exception("Connection failed")):
|
|
async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://test") as client:
|
|
resp = await client.get("/health/gitea")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["status"] == "error"
|
|
assert "Connection failed" in data["error"]
|