aps-agent/tests/golden/test_mes_readiness.py

170 lines
6.4 KiB
Python
Raw Normal View History

# ============================================================
# round-46 方向 LL · 矩阵 73 现场对接准备:MES 适配器对接就绪检查
# 覆盖:未配置 fail-closed 提示 / 配置后字段齐(connectivity=unknown)/
# 探测 ok(连通正常)/ 探测 failed 记录 lastError(连接拒绝、HTTP 503)/
# 网关 GET+P probe 接线 200(未配置 configured=false 不 500)
# 本地 stub:FastAPI + uvicorn 随机端口(不占 8003/5173)
# ============================================================
from __future__ import annotations
import contextlib
import threading
import time
import pytest
import uvicorn
from fastapi import FastAPI, HTTPException
from fastapi.testclient import TestClient
from server.integrations.mes_http import HttpMesClient, reset_http_mes_client
from tests.auth_provider import install_test_auth
from tests.golden.test_mes_http import _free_port, mes_http_stub # noqa: F401 fixture 复用
STABLE_KEYS = {
"configured", "baseUrl", "tokenPresent", "timeoutSec",
"maxRetries", "connectivity", "lastError", "message",
}
def _client(**kwargs):
return HttpMesClient(timeout_sec=2.0, max_retries=1, **kwargs)
@contextlib.contextmanager
def _run_stub(app: FastAPI, port: int):
server = uvicorn.Server(uvicorn.Config(
app, host="127.0.0.1", port=port, log_level="warning"))
thread = threading.Thread(target=server.run, daemon=True)
thread.start()
try:
deadline = 100
while not server.started and deadline > 0:
time.sleep(0.05)
deadline -= 1
yield f"http://127.0.0.1:{port}"
finally:
server.should_exit = True
thread.join(timeout=5)
# ---------------- 未配置:fail-closed 明确提示 ----------------
def test_readiness_unconfigured_fail_closed_hint():
client = HttpMesClient(base_url="", token="")
r = client.readiness()
assert set(r) == STABLE_KEYS
assert r["configured"] is False
assert r["baseUrl"] is None
assert r["tokenPresent"] is False
assert r["connectivity"] is None
assert r["lastError"] is None
assert "未配置 MES_HTTP_BASE_URL" in r["message"]
# probe=True 且未配置:同样不抛、不联网,保持 fail-closed 语义
r2 = client.readiness(probe=True)
assert r2["configured"] is False
assert r2["connectivity"] is None
# ---------------- 配置后:字段齐,未探测 connectivity=unknown ----------------
def test_readiness_configured_fields_before_probe(mes_http_stub):
client = _client(base_url=mes_http_stub["base_url"], token="")
r = client.readiness()
assert set(r) == STABLE_KEYS
assert r["configured"] is True
assert r["baseUrl"] == mes_http_stub["base_url"]
assert r["tokenPresent"] is False
assert r["timeoutSec"] == 2.0
assert r["maxRetries"] == 1
assert r["connectivity"] == "unknown"
assert r["lastError"] is None
assert "已配置" in r["message"]
def test_readiness_token_present_flag(mes_http_stub):
client = _client(base_url=mes_http_stub["base_url"], token="secret-token")
assert client.readiness()["tokenPresent"] is True
# ---------------- 探测 ok ----------------
def test_readiness_probe_ok(mes_http_stub):
client = _client(base_url=mes_http_stub["base_url"], token="")
r = client.readiness(probe=True)
assert set(r) == STABLE_KEYS
assert r["configured"] is True
assert r["connectivity"] == "ok"
assert r["lastError"] is None
assert "连通正常" in r["message"]
# ---------------- 探测 failed:连接拒绝记录 lastError ----------------
def test_readiness_probe_connection_refused_records_last_error():
client = _client(base_url=f"http://127.0.0.1:{_free_port()}")
r = client.readiness(probe=True)
assert r["configured"] is True
assert r["connectivity"] == "failed"
err = r["lastError"]
assert err is not None
assert err["code"] == "MES_HTTP_CONNECT_FAILED"
assert err["statusCode"] == 502
assert err["message"]
assert "连通失败" in r["message"]
# ---------------- 探测 failed:HTTP 5xx 记录 lastError ----------------
def test_readiness_probe_http_error_records_last_error():
app = FastAPI()
@app.get("/health")
async def health() -> dict:
raise HTTPException(status_code=503, detail="upstream maintenance")
with _run_stub(app, _free_port()) as base_url:
client = _client(base_url=base_url)
r = client.readiness(probe=True)
assert r["connectivity"] == "failed"
err = r["lastError"]
assert err["code"] == "MES_HTTP_UPSTREAM_ERROR"
assert err["statusCode"] == 503
# ---------------- 网关接线:GET 只读 + POST probe ----------------
def test_gateway_readiness_and_probe_endpoints(mes_http_stub, monkeypatch):
import server.gateway.app as gw
install_test_auth(monkeypatch, "tenant-readiness-0000000000000000001")
monkeypatch.delenv("MES_HTTP_BASE_URL", raising=False)
client = TestClient(gw.create_app())
login = client.post("/api/auth/login", json={"username": "planner", "password": "test"})
assert login.status_code == 200, login.text
# 未配置:GET 200 + configured=false 明确提示(不 500)
reset_http_mes_client(base_url="", token="")
resp = client.get("/api/integrations/mes/readiness")
assert resp.status_code == 200, resp.text
body = resp.json()
assert set(body) == STABLE_KEYS
assert body["configured"] is False
assert "未配置" in body["message"]
# 配置 stub:GET unknown,POST probe ok
reset_http_mes_client(base_url=mes_http_stub["base_url"], token="")
resp = client.get("/api/integrations/mes/readiness")
assert resp.status_code == 200, resp.text
assert resp.json()["configured"] is True
assert resp.json()["connectivity"] == "unknown"
resp = client.post("/api/integrations/mes/readiness/probe")
assert resp.status_code == 200, resp.text
body = resp.json()
assert body["configured"] is True
assert body["connectivity"] == "ok"
assert body["lastError"] is None
assert "连通正常" in body["message"]
# 配置指向死端口:probe failed 记录 lastError(仍 200)
reset_http_mes_client(base_url=f"http://127.0.0.1:{_free_port()}", token="")
resp = client.post("/api/integrations/mes/readiness/probe")
assert resp.status_code == 200, resp.text
body = resp.json()
assert body["connectivity"] == "failed"
assert body["lastError"]["code"] == "MES_HTTP_CONNECT_FAILED"
assert "连通失败" in body["message"]