Files

102 lines
3.8 KiB
Python

import asyncio
import sys
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
# Contract: Health Endpoint Structure
def test_health_structure():
"""
Contract: /openclaw/health must return:
- ok: bool
- pack: dict (name, version)
- uptime_sec: float
- config: dict (provider, model, etc)
- stats: dict
Justification for Mocking:
We mock the `aiohttp` web stack and service dependencies here to strictly verify the
*API Contract* (JSON schema/shape stability) without requiring a full runtime environment.
This ensures the contract test is fast, deterministic, and only fails on breaking schema changes,
not on environment/dependency issues.
"""
async def _test_logic():
# 1. Mock dependencies to avoid side effects & imports
mock_web = MagicMock()
mock_web.json_response = MagicMock(
side_effect=lambda data, **kwargs: data
) # Returns the data dict directly for inspection
mock_client = MagicMock()
mock_client.get_provider_summary.return_value = {
"provider": "openai",
"model": "gpt-4",
"key_configured": True,
}
with patch.dict(
sys.modules,
{
"aiohttp": MagicMock(web=mock_web),
"aiohttp.web": mock_web,
"services.llm_client": MagicMock(
LLMClient=MagicMock(return_value=mock_client)
),
"services.providers.keys": MagicMock(requires_api_key=lambda p: True),
"services.access_control": MagicMock(is_loopback=lambda ip: True),
"services.metrics": MagicMock(
metrics=MagicMock(
get_snapshot=lambda: {"errors_captured": 0, "logs_processed": 0}
)
),
"services.trace_store": MagicMock(),
"services.log_tail": MagicMock(),
"services.rate_limit": MagicMock(),
"services.redaction": MagicMock(),
},
):
# Mock PACK_* constants in api.routes by patching where they are imported from
with patch("api.routes.PACK_START_TIME", 1000000):
from api.routes import health_handler
# 2. Execute Handler
request = MagicMock()
request.remote = "127.0.0.1"
# Since we mocked metrics and other globals in sys.modules,
# we might need to patch them in api.routes if they were already imported.
# But for this test execution, we are relying on re-import or clean state.
# Safest way: just set the globals in api.routes if they are None
import api.routes
api.routes.web = mock_web
api.routes.metrics = MagicMock(
get_snapshot=lambda: {"errors_captured": 0, "logs_processed": 0}
)
response_data = await health_handler(request)
# 3. Assert Contract
assert response_data["ok"] is True
assert "pack" in response_data
assert "uptime_sec" in response_data
assert "config" in response_data
assert response_data["config"]["provider"] == "openai"
assert response_data["config"]["model"] == "gpt-4"
assert "stats" in response_data
# Run the async test logic synchronously
asyncio.run(_test_logic())
# Contract: Error Response Format
def test_error_response_format():
"""
Contract: All errors must follow {"ok": False, "error": "code"}
"""
# This contract is implicit in many handlers.
# We verify the helper _json_resp structure if accessible, or a known error path.
pass