2026-09-10 15:55:54 +08:00
|
|
|
"""就绪探针(B3)单元测试:Milvus 可用/不可用/客户端缺失/探测超时。"""
|
|
|
|
|
|
|
|
|
|
import asyncio
|
|
|
|
|
from typing import Any
|
|
|
|
|
|
2026-09-09 23:40:35 +08:00
|
|
|
import pytest
|
|
|
|
|
|
2026-09-10 15:55:54 +08:00
|
|
|
from app.service import health_service
|
2026-09-09 23:40:35 +08:00
|
|
|
from app.service.health_service import HealthService
|
|
|
|
|
|
|
|
|
|
|
2026-09-10 15:55:54 +08:00
|
|
|
class FakeMilvusClient:
|
|
|
|
|
def __init__(self, *, version: Any = "pkg/v2.5.4", error: Exception | None = None,
|
|
|
|
|
delay: float = 0.0) -> None:
|
|
|
|
|
self._version = version
|
|
|
|
|
self._error = error
|
|
|
|
|
self._delay = delay
|
|
|
|
|
self.closed = False
|
|
|
|
|
|
|
|
|
|
async def get_server_version(self) -> Any:
|
|
|
|
|
if self._delay:
|
|
|
|
|
await asyncio.sleep(self._delay)
|
|
|
|
|
if self._error is not None:
|
|
|
|
|
raise self._error
|
|
|
|
|
return self._version
|
|
|
|
|
|
|
|
|
|
async def close(self) -> None:
|
|
|
|
|
self.closed = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def patch_client(monkeypatch: pytest.MonkeyPatch, client: Any) -> None:
|
|
|
|
|
monkeypatch.setattr(health_service, "_create_milvus_client", lambda uri, token: client)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_available_milvus_reports_ok(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
|
|
|
client = FakeMilvusClient()
|
|
|
|
|
patch_client(monkeypatch, client)
|
|
|
|
|
ready, state = await HealthService()._milvus_ready()
|
|
|
|
|
assert (ready, state) == (True, "ok")
|
|
|
|
|
assert client.closed is True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("error", [ConnectionError("refused"), RuntimeError("grpc down")])
|
|
|
|
|
async def test_unavailable_milvus_reports_unavailable(
|
|
|
|
|
monkeypatch: pytest.MonkeyPatch, error: Exception
|
|
|
|
|
) -> None:
|
|
|
|
|
client = FakeMilvusClient(error=error)
|
|
|
|
|
patch_client(monkeypatch, client)
|
|
|
|
|
ready, state = await HealthService()._milvus_ready()
|
|
|
|
|
assert (ready, state) == (False, "unavailable")
|
|
|
|
|
assert client.closed is True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_missing_pymilvus_reports_client_missing(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
|
|
|
def missing(uri: str, token: str) -> Any:
|
|
|
|
|
raise ImportError("No module named 'pymilvus'")
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(health_service, "_create_milvus_client", missing)
|
|
|
|
|
ready, state = await HealthService()._milvus_ready()
|
|
|
|
|
assert (ready, state) == (False, "client_missing")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_probe_timeout_is_bounded(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
|
|
|
client = FakeMilvusClient(delay=5.0)
|
|
|
|
|
patch_client(monkeypatch, client)
|
|
|
|
|
ready, state = await HealthService(milvus_timeout_seconds=0.05)._milvus_ready()
|
|
|
|
|
assert (ready, state) == (False, "timeout")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_ready_reflects_real_milvus_probe(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
|
|
|
client = FakeMilvusClient(error=ConnectionError("milvus stopped"))
|
|
|
|
|
patch_client(monkeypatch, client)
|
|
|
|
|
|
|
|
|
|
async def redis_ok(_self: HealthService) -> bool:
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(HealthService, "_redis_ready", redis_ok)
|
|
|
|
|
result = await HealthService().ready()
|
|
|
|
|
assert result["checks"]["milvus"] is False
|
|
|
|
|
assert result["probes"]["milvus"] == "unavailable"
|
|
|
|
|
assert result["status"] == "degraded"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_ready_is_ready_when_all_probes_pass(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
|
|
|
patch_client(monkeypatch, FakeMilvusClient())
|
|
|
|
|
|
|
|
|
|
async def redis_ok(_self: HealthService) -> bool:
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(HealthService, "_redis_ready", redis_ok)
|
|
|
|
|
result = await HealthService().ready()
|
|
|
|
|
assert result["checks"]["milvus"] is True
|
|
|
|
|
assert result["probes"]["milvus"] == "ok"
|
|
|
|
|
assert result["checks"]["mysql"] is True
|
|
|
|
|
|
|
|
|
|
|
2026-09-09 23:40:35 +08:00
|
|
|
async def test_redis_failure_marks_readiness_degraded(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
|
|
|
async def unavailable(self: HealthService) -> bool:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(HealthService, "_redis_ready", unavailable)
|
2026-09-10 15:55:54 +08:00
|
|
|
patch_client(monkeypatch, FakeMilvusClient())
|
2026-09-09 23:40:35 +08:00
|
|
|
service = HealthService()
|
|
|
|
|
result = await service.ready()
|
|
|
|
|
assert result["checks"]["redis"] is False
|
|
|
|
|
assert result["status"] == "degraded"
|