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