118 lines
3.4 KiB
Python
118 lines
3.4 KiB
Python
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
|
|
from app.api.v1.endpoints import copilot as copilot_endpoint
|
|
|
|
|
|
class _FakeStreamResponse:
|
|
def __init__(self, status_code: int, lines: list[str] | None = None, body: bytes = b""):
|
|
self.status_code = status_code
|
|
self._lines = lines or []
|
|
self._body = body
|
|
|
|
async def __aenter__(self):
|
|
return self
|
|
|
|
async def __aexit__(self, exc_type, exc, tb):
|
|
return None
|
|
|
|
async def aread(self) -> bytes:
|
|
return self._body
|
|
|
|
async def aiter_lines(self):
|
|
for line in self._lines:
|
|
yield line
|
|
|
|
|
|
class _FakeAsyncClient:
|
|
response: _FakeStreamResponse
|
|
captured: dict
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self._kwargs = kwargs
|
|
|
|
async def __aenter__(self):
|
|
return self
|
|
|
|
async def __aexit__(self, exc_type, exc, tb):
|
|
return None
|
|
|
|
def stream(self, method: str, url: str, json: dict, headers: dict):
|
|
_FakeAsyncClient.captured = {
|
|
"method": method,
|
|
"url": url,
|
|
"json": json,
|
|
"headers": headers,
|
|
"client_kwargs": self._kwargs,
|
|
}
|
|
return _FakeAsyncClient.response
|
|
|
|
|
|
def _build_client(monkeypatch) -> TestClient:
|
|
app = FastAPI()
|
|
app.include_router(copilot_endpoint.router, prefix="/api/v1/copilot")
|
|
app.dependency_overrides[copilot_endpoint.get_current_keycloak_username] = (
|
|
lambda: "tester"
|
|
)
|
|
monkeypatch.setattr(copilot_endpoint.httpx, "AsyncClient", _FakeAsyncClient)
|
|
return TestClient(app)
|
|
|
|
|
|
def test_chat_stream_forwards_auth_and_payload(monkeypatch):
|
|
_FakeAsyncClient.response = _FakeStreamResponse(
|
|
status_code=200,
|
|
lines=[
|
|
'event: token',
|
|
'data: {"conversationId":"c1","content":"hello"}',
|
|
"",
|
|
'event: done',
|
|
'data: {"conversationId":"c1"}',
|
|
"",
|
|
],
|
|
)
|
|
client = _build_client(monkeypatch)
|
|
|
|
response = client.post(
|
|
"/api/v1/copilot/chat/stream",
|
|
json={"message": "hi", "conversation_id": "conv-1"},
|
|
headers={
|
|
"Authorization": "Bearer keycloak-token",
|
|
"X-Project-Id": "project-a",
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
assert "text/event-stream" in response.headers["content-type"]
|
|
assert "event: token" in response.text
|
|
assert "event: done" in response.text
|
|
|
|
captured = _FakeAsyncClient.captured
|
|
assert captured["method"] == "POST"
|
|
assert captured["url"].endswith("/chat/stream")
|
|
assert captured["headers"]["authorization"] == "Bearer keycloak-token"
|
|
assert captured["headers"]["x-project-id"] == "project-a"
|
|
assert captured["json"] == {
|
|
"message": "hi",
|
|
"conversationId": "conv-1",
|
|
"userId": "tester",
|
|
}
|
|
|
|
|
|
def test_chat_stream_emits_error_event_when_upstream_fails(monkeypatch):
|
|
_FakeAsyncClient.response = _FakeStreamResponse(
|
|
status_code=401,
|
|
body=b"upstream unauthorized",
|
|
)
|
|
client = _build_client(monkeypatch)
|
|
|
|
response = client.post(
|
|
"/api/v1/copilot/chat/stream",
|
|
json={"message": "hi"},
|
|
headers={"Authorization": "Bearer keycloak-token"},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
assert "event: error" in response.text
|
|
assert "Copilot sidecar request failed" in response.text
|
|
assert '"status": 401' in response.text
|