feat(observability): /phoenix/ 중계 — 바깥 포트가 8914 뿐이라 백엔드 뒤에 Phoenix 화면 붙임(Basic 잠금, 접두어 떼고 전달). 로컬 e2e(HTML·자산·GraphQL·REST) 통과
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
8268947fc7
commit
ac7a459dfc
@@ -0,0 +1,60 @@
|
||||
"""/phoenix/… 중계 — 경로·쿼리 전달, Basic 잠금, 상류 죽으면 502."""
|
||||
|
||||
import base64
|
||||
import json
|
||||
|
||||
import httpx
|
||||
from django.test import AsyncClient, override_settings
|
||||
|
||||
from apps.gateway import phoenix_proxy
|
||||
|
||||
PX = {"host": "", "public_key": "", "secret_key": "", "gateway": False, "phoenix": "http://127.0.0.1:8915", "project": "codeassist"}
|
||||
|
||||
|
||||
def _upstream(monkeypatch, handler):
|
||||
calls = []
|
||||
|
||||
def h(req):
|
||||
calls.append(req)
|
||||
r = handler(req) # content= 로 만든 응답은 stream 으로 못 읽어서(StreamConsumed) 바꿔 끼움
|
||||
return httpx.Response(r.status_code, stream=httpx.ByteStream(r.content), headers=r.headers)
|
||||
|
||||
monkeypatch.setattr(phoenix_proxy, "TRANSPORT", httpx.MockTransport(h))
|
||||
return calls
|
||||
|
||||
|
||||
@override_settings(LANGFUSE=PX, PHOENIX_UI_PASSWORD="")
|
||||
async def test_forwards_path_query_and_body(monkeypatch):
|
||||
calls = _upstream(monkeypatch, lambda r: httpx.Response(200, content=b"<html>ok</html>", headers={"content-type": "text/html"}))
|
||||
resp = await AsyncClient().post("/phoenix/graphql?x=1", data=json.dumps({"q": 1}), content_type="application/json")
|
||||
body = b"".join([c async for c in resp.streaming_content])
|
||||
assert resp.status_code == 200 and body == b"<html>ok</html>" and resp["Content-Type"] == "text/html"
|
||||
assert str(calls[0].url) == "http://127.0.0.1:8915/graphql?x=1" and calls[0].method == "POST" and json.loads(calls[0].content) == {"q": 1}
|
||||
resp = await AsyncClient().get("/phoenix")
|
||||
b"".join([c async for c in resp.streaming_content])
|
||||
assert str(calls[1].url) == "http://127.0.0.1:8915/"
|
||||
|
||||
|
||||
@override_settings(LANGFUSE=PX, PHOENIX_UI_PASSWORD="s3cret")
|
||||
async def test_basic_auth_gate(monkeypatch):
|
||||
calls = _upstream(monkeypatch, lambda r: httpx.Response(200, content=b"ok"))
|
||||
resp = await AsyncClient().get("/phoenix/")
|
||||
assert resp.status_code == 401 and resp["WWW-Authenticate"].startswith("Basic") and calls == []
|
||||
ok = "Basic " + base64.b64encode(b"admin:s3cret").decode()
|
||||
resp = await AsyncClient().get("/phoenix/", headers={"Authorization": ok})
|
||||
b"".join([c async for c in resp.streaming_content])
|
||||
assert resp.status_code == 200 and "authorization" not in {k.lower() for k in calls[0].headers}
|
||||
|
||||
|
||||
@override_settings(LANGFUSE=PX, PHOENIX_UI_PASSWORD="")
|
||||
async def test_upstream_down_is_502(monkeypatch):
|
||||
def boom(r):
|
||||
raise httpx.ConnectError("down")
|
||||
|
||||
_upstream(monkeypatch, boom)
|
||||
assert (await AsyncClient().get("/phoenix/")).status_code == 502
|
||||
|
||||
|
||||
@override_settings(LANGFUSE={**PX, "phoenix": ""}, PHOENIX_UI_PASSWORD="")
|
||||
async def test_not_configured_is_503():
|
||||
assert (await AsyncClient().get("/phoenix/")).status_code == 503
|
||||
Reference in New Issue
Block a user