fix(tests): stabilize escalation SSE backend tests

Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
2026-04-27 19:47:43 -04:00
parent ba46fc5644
commit bc15952857
7 changed files with 153 additions and 73 deletions

View File

@@ -68,6 +68,21 @@ async def test_subscriber_in_other_account_does_not_receive():
await bus.unsubscribe(account_b, q_b)
@pytest.mark.asyncio
async def test_publish_normalizes_string_uuid_account_id():
"""ORM-created objects can briefly carry string UUIDs in-memory."""
bus = EscalationBus()
account = uuid4()
queue = await bus.subscribe(account)
try:
delivered = await bus.publish(str(account), {"type": "x"})
assert delivered == 1
event = await asyncio.wait_for(queue.get(), timeout=1.0)
assert event == {"type": "x"}
finally:
await bus.unsubscribe(str(account), queue)
@pytest.mark.asyncio
async def test_unsubscribe_drops_subscriber_count_to_zero():
bus = EscalationBus()

View File

@@ -9,6 +9,26 @@ from app.models.user import User
from app.services.handoff_manager import HandoffManager
@pytest.fixture(autouse=True)
def stub_ai_assessment():
"""Keep handoff tests focused on handoff behavior, not external AI calls."""
with patch.object(
HandoffManager,
"_generate_ai_assessment",
new=AsyncMock(
return_value=(
"Stub escalation assessment",
{
"likely_cause": "Stub",
"suggested_steps": [],
"confidence": "medium",
},
)
),
):
yield
@pytest.mark.asyncio
async def test_create_park_handoff(client: AsyncClient, test_user, auth_headers, test_db):
"""Parking a session creates a handoff with snapshot."""

View File

@@ -1,12 +1,41 @@
"""API endpoint tests for session handoffs."""
from unittest.mock import AsyncMock, patch
from uuid import UUID as PyUUID
import pytest
from httpx import AsyncClient
from sqlalchemy import select
from app.api.endpoints.session_handoffs import stream_escalations
from app.core.escalation_bus import bus as escalation_bus
from app.models.ai_session import AISession
from app.models.user import User
from app.services.handoff_manager import HandoffManager
class _ConnectedRequest:
async def is_disconnected(self) -> bool:
return False
@pytest.fixture(autouse=True)
def stub_ai_assessment():
"""Endpoint tests should not wait on the external AI assessment path."""
with patch.object(
HandoffManager,
"_generate_ai_assessment",
new=AsyncMock(
return_value=(
"Stub escalation assessment",
{
"likely_cause": "Stub",
"suggested_steps": [],
"confidence": "medium",
},
)
),
):
yield
@pytest.mark.asyncio
@@ -137,23 +166,30 @@ async def test_escalations_stream_returns_sse_content_type(
):
"""Engineer/owner can open the SSE stream and gets text/event-stream
plus an initial `ready` event. Read just enough bytes to confirm the
handshake — the full pub/sub flow is covered by the bus + dispatcher
tests separately."""
async with client.stream(
"GET",
"/api/v1/ai-sessions/escalations/stream",
headers=auth_headers,
) as resp:
assert resp.status_code == 200
assert resp.headers["content-type"].startswith("text/event-stream")
# First chunk must contain the ready event.
first = b""
async for chunk in resp.aiter_bytes():
first += chunk
if b"event: ready" in first and b"\n\n" in first:
break
assert b"event: ready" in first
assert b'"account_id"' in first
handshake — the full pub/sub flow is covered by the bus + dispatcher tests
separately.
Do not use `client.stream()` here: HTTPX's ASGITransport buffers the whole
response body before returning, which hangs forever for an infinite SSE
stream.
"""
user_id = PyUUID(test_user["user_data"]["id"])
user = (
await test_db.execute(select(User).where(User.id == user_id))
).scalar_one()
resp = await stream_escalations(_ConnectedRequest(), current_user=user)
assert resp.media_type == "text/event-stream"
body_iterator = resp.body_iterator
try:
first = await anext(body_iterator)
finally:
await body_iterator.aclose()
assert "event: ready" in first
assert '"account_id"' in first
assert escalation_bus.subscriber_count(user.account_id) == 0
@pytest.mark.asyncio