3eb439b5ae
Reviewed-on: #114
112 lines
3.8 KiB
Python
112 lines
3.8 KiB
Python
"""/api/v1/intent/recognize 和 /api/v1/price/step 透传端点测试。
|
|
|
|
mock 掉对 pricebot 的 httpx 调用,验证(两个端点参数化同跑):
|
|
1. 无 token 也能用(MVP 不鉴权,同 coupon/step)
|
|
2. pricebot 200 → 响应原样透传,请求 body 原样转发到对应上游路径(去掉 /v1)
|
|
3. pricebot 5xx → 502
|
|
4. pricebot 网络错误 → 502
|
|
5. 非 JSON body → 400
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
# (客户端端点, 期望转发到的 pricebot 上游路径)
|
|
ENDPOINTS = [
|
|
("/api/v1/intent/recognize", "/api/intent/recognize"),
|
|
("/api/v1/price/step", "/api/price/step"),
|
|
]
|
|
|
|
|
|
def _stub_screen_state() -> dict:
|
|
return {
|
|
"screen": {"width": 1080, "height": 2340, "density": 3.0},
|
|
"foreground": {"package": "com.sankuai.meituan", "activity": ""},
|
|
"windows": [],
|
|
}
|
|
|
|
|
|
def _stub_body() -> dict:
|
|
return {
|
|
"device_id": "test-device",
|
|
"trace_id": "test-trace-1",
|
|
"step": 0,
|
|
"query": "海底捞",
|
|
"screen_state": _stub_screen_state(),
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize("path,upstream", ENDPOINTS)
|
|
def test_passes_body_through(client, path, upstream) -> None:
|
|
"""无 token(软鉴权放行)+ pricebot 200 → 响应透传(+ app-server 注入 trace_id),
|
|
body 原样转发(自带 trace_id 时不重签),URL 去掉 /v1。"""
|
|
fake_resp = {
|
|
"success": True,
|
|
"action": {"command": "wait", "params": {"duration_ms": 500}},
|
|
"continue": True,
|
|
}
|
|
captured: dict = {}
|
|
|
|
async def fake_post(self, url, content=None, **kw):
|
|
# 透传壳用 content=raw(bytes)转发,不是 json=;这里捕获 content 解回来核对
|
|
captured["url"] = url
|
|
captured["content"] = content
|
|
mock_resp = MagicMock()
|
|
mock_resp.status_code = 200
|
|
mock_resp.json = lambda: dict(fake_resp) # 每次新副本(端点会 setdefault trace_id 进去)
|
|
return mock_resp
|
|
|
|
with patch.object(httpx.AsyncClient, "post", fake_post):
|
|
r = client.post(path, json=_stub_body())
|
|
|
|
assert r.status_code == 200, r.text
|
|
body = r.json()
|
|
assert body["success"] is True
|
|
assert body["action"] == fake_resp["action"]
|
|
assert body["trace_id"] == "test-trace-1" # 自带 trace_id → 原样回传, 不 mint
|
|
# body 原样转发(content=raw bytes;自带 trace_id 时不重新序列化)
|
|
assert json.loads(captured["content"]) == _stub_body()
|
|
assert captured["url"].endswith(upstream) # /api/v1/xxx → /api/xxx
|
|
|
|
|
|
@pytest.mark.parametrize("path,upstream", ENDPOINTS)
|
|
def test_pricebot_5xx_returns_502(client, path, upstream) -> None:
|
|
async def fake_post(self, url, json=None, **kw):
|
|
mock_resp = MagicMock()
|
|
mock_resp.status_code = 503
|
|
mock_resp.text = "service unavailable"
|
|
return mock_resp
|
|
|
|
with patch.object(httpx.AsyncClient, "post", fake_post):
|
|
r = client.post(path, json=_stub_body())
|
|
|
|
assert r.status_code == 502
|
|
assert "pricebot upstream returned 503" in r.json()["detail"]
|
|
|
|
|
|
@pytest.mark.parametrize("path,upstream", ENDPOINTS)
|
|
def test_pricebot_unreachable_returns_502(client, path, upstream) -> None:
|
|
async def fake_post(self, url, json=None, **kw):
|
|
raise httpx.ConnectError("connection refused")
|
|
|
|
with patch.object(httpx.AsyncClient, "post", fake_post):
|
|
r = client.post(path, json=_stub_body())
|
|
|
|
assert r.status_code == 502
|
|
assert "pricebot upstream unreachable" in r.json()["detail"]
|
|
|
|
|
|
@pytest.mark.parametrize("path,upstream", ENDPOINTS)
|
|
def test_invalid_json_body(client, path, upstream) -> None:
|
|
r = client.post(
|
|
path,
|
|
headers={"Content-Type": "application/json"},
|
|
content=b"not-json",
|
|
)
|
|
assert r.status_code == 400
|
|
assert "invalid json body" in r.json()["detail"]
|