"""就绪探针(B3)单元测试:Milvus 可用/不可用/客户端缺失/探测超时。""" import asyncio from typing import Any import pytest from app.service import health_service from app.service.health_service import HealthService 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 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) patch_client(monkeypatch, FakeMilvusClient()) service = HealthService() result = await service.ready() assert result["checks"]["redis"] is False assert result["status"] == "degraded"