aps-agent/tests/golden/test_mes_readiness.py

170 lines
6.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ============================================================
# 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"]