"""Task A5 — REST message route. The route consumes `ConversationManager.stream_process_message` and returns the collected reply plus `ended`/`intent_level`. Lead email is triggered inside `ConversationManager` when contact info is detected (contact-driven-lead-email plan) — the route carries no email logic. """ from app.main import app # noqa: F401 — ensure router registration from app.engine.services import conversation as conv_mod def test_message_replies_and_ended_flag(client, monkeypatch): async def fake_stream(self, sid, content): yield {"type": "stream_start"} yield {"type": "stream_chunk", "content": "Hi"} yield {"type": "stream_chunk", "content": " there"} yield {"type": "stream_end"} yield {"type": "evaluated", "ended": False, "intent_level": "pending"} monkeypatch.setattr( conv_mod.ConversationManager, "stream_process_message", fake_stream ) sid = client.post("/api/chat/session", json={}).json()["session_id"] r = client.post( "/api/chat/message", json={"session_id": sid, "content": "hello"} ) assert r.status_code == 200 d = r.json() assert d["reply"] == "Hi there" assert d["ended"] is False def test_404_unknown_session(client): r = client.post( "/api/chat/message", json={"session_id": "nope", "content": "x"} ) assert r.status_code == 404 def test_rate_limit_429(client, monkeypatch): async def fake_stream(self, sid, content): yield {"type": "stream_chunk", "content": "ok"} yield {"type": "evaluated", "ended": False, "intent_level": "pending"} monkeypatch.setattr( conv_mod.ConversationManager, "stream_process_message", fake_stream ) sid = client.post("/api/chat/session", json={}).json()["session_id"] for _ in range(30): client.post( "/api/chat/message", json={"session_id": sid, "content": "x"} ).raise_for_status() over = client.post( "/api/chat/message", json={"session_id": sid, "content": "x"} ) assert over.status_code == 429