"""外部监控接口:有在线 worker 才算服务正常;故障模拟只改探活响应。""" import asyncio import pytest from gateway import app as gateway_app from gateway.app import monitor, monitor_fault, monitor_payload class _Req: def __init__(self, body=None, token="secret", raw=None): self._body = body self._raw = raw self.headers = {"X-Internal-Token": token} if token is not None else {} async def json(self): if self._raw is not None: raise self._raw return self._body @pytest.fixture(autouse=True) def _reset_monitor_drill(): gateway_app._monitor_drill["enabled"] = False yield gateway_app._monitor_drill["enabled"] = False def test_monitor_ok_when_any_worker_online(): body = monitor_payload({ "total": 5, "healthy": 1, "primary_total": 3, "primary_healthy": 0, }) assert body["status"] == "ok" assert body["workers_healthy"] == 1 assert "message" not in body def test_monitor_unhealthy_when_none_online(): body = monitor_payload({ "total": 5, "healthy": 0, "primary_total": 3, "primary_healthy": 0, }) assert body["status"] == "unhealthy" assert body["message"] == "没有可用的 worker" assert body["workers_total"] == 5 def test_monitor_unhealthy_when_pool_empty(): body = monitor_payload({"total": 0, "healthy": 0}) assert body["status"] == "unhealthy" def test_monitor_http_503_when_no_worker(monkeypatch): monkeypatch.setattr( "gateway.app._get_pool_status_safe", lambda: {"total": 2, "healthy": 0, "primary_total": 2, "primary_healthy": 0}, ) resp = asyncio.run(monitor()) assert resp.status_code == 503 assert resp.body assert b'"status":"unhealthy"' in resp.body or b'"status": "unhealthy"' in resp.body def test_monitor_http_200_when_worker_online(monkeypatch): monkeypatch.setattr( "gateway.app._get_pool_status_safe", lambda: {"total": 2, "healthy": 2, "primary_total": 2, "primary_healthy": 1}, ) body = asyncio.run(monitor()) assert body["status"] == "ok" assert body["workers_healthy"] == 2 def test_monitor_payload_simulated_and_really_down(): body = monitor_payload( {"total": 5, "healthy": 0, "primary_total": 3, "primary_healthy": 0}, simulated=True, ) assert body["status"] == "unhealthy" assert body["simulated"] is True assert body["workers_healthy"] == 0 assert "没有可用的 worker" in body["message"] def test_monitor_payload_simulated_keeps_real_counts(): body = monitor_payload( {"total": 5, "healthy": 3, "primary_total": 3, "primary_healthy": 3}, simulated=True, ) assert body["status"] == "unhealthy" assert body["simulated"] is True assert body["workers_healthy"] == 3 assert "模拟故障" in body["message"] def _patch_token(monkeypatch, password="secret"): monkeypatch.setattr( "gateway.config.get_config", lambda: {"shared_password": password}, ) def test_fault_requires_token(monkeypatch): _patch_token(monkeypatch) resp = asyncio.run(monitor_fault(_Req(body={"enabled": True}, token="nope"))) assert resp.status_code == 401 assert gateway_app._monitor_drill["enabled"] is False def test_fault_rejects_non_bool(monkeypatch): _patch_token(monkeypatch) resp = asyncio.run(monitor_fault(_Req(body={"enabled": "true"}))) assert resp.status_code == 400 assert gateway_app._monitor_drill["enabled"] is False def test_fault_on_makes_monitor_503_then_off_restores(monkeypatch): monkeypatch.setattr( "gateway.app._get_pool_status_safe", lambda: {"total": 5, "healthy": 3, "primary_total": 3, "primary_healthy": 3}, ) _patch_token(monkeypatch) opened = asyncio.run(monitor_fault(_Req(body={"enabled": True}))) assert opened["simulated"] is True failed = asyncio.run(monitor()) assert failed.status_code == 503 assert b'"simulated":true' in failed.body or b'"simulated": true' in failed.body assert b'"workers_healthy":3' in failed.body or b'"workers_healthy": 3' in failed.body closed = asyncio.run(monitor_fault(_Req(body={"enabled": False}))) assert closed["simulated"] is False ok = asyncio.run(monitor()) assert ok["status"] == "ok" assert "simulated" not in ok