synapse / backend /tests /test_chat.py
adwitiyashukla's picture
pushed Synapse, an agentic AI assistant with hybrid RAG
b2c1c67
Raw
History Blame Contribute Delete
5.23 kB
"""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