Spaces:
Running
Running
| """Chat streaming tests using the scripted fake provider.""" | |
| import json | |
| from httpx import AsyncClient | |
| from tests.conftest import FakeProvider, register_and_login | |
| def parse_sse(raw: str) -> list[dict]: | |
| events = [] | |
| for block in raw.split("\n\n"): | |
| block = block.strip() | |
| if block.startswith("data: "): | |
| events.append(json.loads(block[len("data: ") :])) | |
| return events | |
| async def create_session(client: AsyncClient, headers: dict) -> str: | |
| response = await client.post("/api/sessions", json={}, headers=headers) | |
| return response.json()["id"] | |
| async def test_simple_chat_stream( | |
| client: AsyncClient, fake_provider: FakeProvider | |
| ) -> None: | |
| fake_provider.turns = [{"text": "The answer is 42."}] | |
| headers = await register_and_login(client) | |
| session_id = await create_session(client, headers) | |
| response = await client.post( | |
| f"/api/chat/{session_id}", json={"content": "What is the answer?"}, headers=headers | |
| ) | |
| assert response.status_code == 200 | |
| events = parse_sse(response.text) | |
| types = [e["type"] for e in events] | |
| assert "token" in types | |
| assert types[-1] == "done" | |
| text = "".join(e["text"] for e in events if e["type"] == "token") | |
| assert "42" in text | |
| usage = next(e for e in events if e["type"] == "usage") | |
| assert usage["input_tokens"] == 100 | |
| assert usage["output_tokens"] == 20 | |
| assert usage["cost_usd"] > 0 | |
| title_event = next(e for e in events if e["type"] == "title") | |
| assert title_event["title"] == "Fake title" | |
| # Both messages persisted | |
| messages = ( | |
| await client.get(f"/api/sessions/{session_id}/messages", headers=headers) | |
| ).json() | |
| assert [m["role"] for m in messages] == ["user", "assistant"] | |
| assert messages[1]["cost_usd"] > 0 | |
| async def test_chat_with_tool_call( | |
| client: AsyncClient, fake_provider: FakeProvider | |
| ) -> None: | |
| fake_provider.turns = [ | |
| {"tool_calls": [("calculator", '{"expression": "6*7"}')]}, | |
| {"text": "The result is 42."}, | |
| ] | |
| headers = await register_and_login(client) | |
| session_id = await create_session(client, headers) | |
| response = await client.post( | |
| f"/api/chat/{session_id}", json={"content": "compute 6*7"}, headers=headers | |
| ) | |
| events = parse_sse(response.text) | |
| types = [e["type"] for e in events] | |
| assert "tool_start" in types and "tool_end" in types | |
| tool_end = next(e for e in events if e["type"] == "tool_end") | |
| assert tool_end["name"] == "calculator" | |
| assert "42" in tool_end["result_preview"] | |
| # Tool result was sent back to the model on the second round | |
| second_call_messages = fake_provider.calls[1] | |
| assert any(m.get("role") == "tool" and "42" in m["content"] for m in second_call_messages) | |
| # Tool usage persisted on the assistant message | |
| messages = ( | |
| await client.get(f"/api/sessions/{session_id}/messages", headers=headers) | |
| ).json() | |
| tool_log = json.loads(messages[1]["tool_calls_json"]) | |
| assert tool_log[0]["name"] == "calculator" | |
| async def test_provider_extra_fields_are_echoed_back( | |
| client: AsyncClient, fake_provider: FakeProvider | |
| ) -> None: | |
| """Gemini attaches thought signatures to tool calls; they must be resent | |
| verbatim on the next round or the API rejects the request.""" | |
| signature = {"extra_content": {"google": {"thought_signature": "sig-abc-123"}}} | |
| fake_provider.turns = [ | |
| {"tool_calls": [("calculator", '{"expression": "1+1"}', signature)]}, | |
| {"text": "The result is 2."}, | |
| ] | |
| headers = await register_and_login(client) | |
| session_id = await create_session(client, headers) | |
| response = await client.post( | |
| f"/api/chat/{session_id}", json={"content": "1+1?"}, headers=headers | |
| ) | |
| assert response.status_code == 200 | |
| second_round = fake_provider.calls[1] | |
| assistant = next(m for m in second_round if m.get("tool_calls")) | |
| echoed = assistant["tool_calls"][0] | |
| assert echoed["extra_content"] == {"google": {"thought_signature": "sig-abc-123"}} | |
| async def test_chat_rejects_foreign_session( | |
| client: AsyncClient, fake_provider: FakeProvider | |
| ) -> None: | |
| headers_a = await register_and_login(client, "a@example.com") | |
| headers_b = await register_and_login(client, "b@example.com") | |
| session_id = await create_session(client, headers_a) | |
| response = await client.post( | |
| f"/api/chat/{session_id}", json={"content": "hi"}, headers=headers_b | |
| ) | |
| assert response.status_code == 404 | |
| async def test_analytics_reflect_chat( | |
| client: AsyncClient, fake_provider: FakeProvider | |
| ) -> None: | |
| fake_provider.turns = [{"text": "Hello there."}] | |
| headers = await register_and_login(client) | |
| session_id = await create_session(client, headers) | |
| await client.post(f"/api/chat/{session_id}", json={"content": "hi"}, headers=headers) | |
| overview = (await client.get("/api/analytics/overview", headers=headers)).json() | |
| assert overview["total_messages"] == 2 | |
| assert overview["total_sessions"] == 1 | |
| assert overview["input_tokens"] == 100 | |
| assert overview["output_tokens"] == 20 | |
| assert overview["cost_usd"] > 0 | |
| assert overview["models"][0]["model"] == "gpt-5.6-luna" | |
| assert len(overview["daily"]) == 30 | |