"""FastAPI 网关测试:v1 legacy 端点保持 + v2 端点(封闭,注入 mock 管线)。""" import pytest pytest.importorskip("fastapi") pytest.importorskip("httpx") from fastapi.testclient import TestClient import gateway.api as ga from gateway.api import app @pytest.fixture() def client(): return TestClient(app) @pytest.fixture() def v2_client(client): # 用 mock worker 构建真实 v2 管线并注入(无需 API key / 真实模型) pipe = ga.build_v2_pipeline(worker_cfg_override={"backend": "mock"}) ga.set_pipeline(pipe) return client def test_health(client): resp = client.get("/health") assert resp.status_code == 200 data = resp.json() assert data["status"] == "ok" assert "code" in data["domains"] def test_chat_legacy(client): resp = client.post("/chat/legacy", json={"query": "用 Python 写一个快速排序函数"}) assert resp.status_code == 200 data = resp.json() assert data["response"] assert data["domain"] == "code" assert "route" in data def test_chat_v2(v2_client): # POST /chat 立即返回 request_id(异步协议) resp = v2_client.post("/chat", json={"query": "请介绍快速排序算法"}) assert resp.status_code == 200 data = resp.json() assert "request_id" in data assert data["status"] == "pending" # 轮询 /runs/{id}/status 直到完成 import time for _ in range(50): # 最多 5s time.sleep(0.1) status_resp = v2_client.get(f"/runs/{data['request_id']}/status") assert status_resp.status_code == 200 s = status_resp.json() if s["status"] in ("done", "failed"): break assert s["status"] == "done", f"期望 done,实际 {s['status']},error={s.get('error')}" assert s["response"] assert "pipeline_status" in s assert s["pipeline_status"] in ("fast_path", "done", "escalated") # fast_path 不写 workspace.json,所以 workspace_path 可能为 None if s["pipeline_status"] != "fast_path": assert s["workspace_path"] is not None def test_chat_empty_query(client): resp = client.post("/chat", json={"query": ""}) assert resp.status_code == 422 def test_metrics(client): resp = client.get("/metrics") assert resp.status_code == 200 data = resp.json() assert "router" in data assert "cache" in data assert "v2" in data assert "review" in data def test_config_get_put_reset(client): # GET 默认 r = client.get("/config") assert r.status_code == 200 assert r.json()["worker"]["backend"] in ("llama_server", "openai", "mock") # PUT 更新 worker r2 = client.put("/config", json={"worker": {"backend": "mock", "temperature": 0.5}}) assert r2.status_code == 200 assert r2.json()["worker"]["temperature"] == 0.5 # 重置 r3 = client.post("/config/reset") assert r3.status_code == 200 assert r3.json()["worker"]["backend"] == "llama_server" def test_workspace_not_found(client): resp = client.get("/runs/nonexistent/workspace") assert resp.status_code == 404 def test_web_ui_served(client): resp = client.get("/") assert resp.status_code == 200 assert resp.headers["content-type"].startswith("text/html") # Vue SPA:由 Vite 生成,特征是
和 /static/assets/ 引用 assert '
' in resp.text assert '/static/assets/' in resp.text def test_review_flow(client): q = ga.get_review() rid = q.enqueue("req-x", "q", "ans", tags=["safety"], reason="test") assert q.count() >= 1 resp = client.get("/review/queue") assert resp.status_code == 200 resp2 = client.post(f"/review/{rid}", params={"verdict": "approve"}) assert resp2.status_code == 200 assert resp2.json()["ok"] is True