Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 29 additions & 27 deletions autobot-backend/api/realtime_session_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,13 @@ def _form(sdp: str = _SDP_OFFER, session: str = _SESSION_JSON) -> dict:


def _mock_upstream(status: int = 200, body: bytes = _SDP_ANSWER):
"""Return a mock aiohttp response context-manager."""
"""Return a mock aiohttp response, itself the tracked_request() async CM.

#12979: OpenAIRealtimeProvider._post() now routes through the shared
pool's get_http_client().tracked_request() rather than a private
ClientSession, so this is patched directly (see _patch_http_client())
instead of patching aiohttp.ClientSession.
"""
resp = MagicMock()
resp.status = status
resp.read = AsyncMock(return_value=body)
Expand All @@ -64,15 +70,11 @@ def _mock_upstream(status: int = 200, body: bytes = _SDP_ANSWER):
return cm


def _mock_session(upstream_cm):
"""Return a mock aiohttp.ClientSession context-manager."""
session = MagicMock()
session.post = MagicMock(return_value=upstream_cm)
def _patch_http_client(upstream_cm):
"""Patch the shared HTTPClientManager singleton's tracked_request()."""
from autobot_shared.http_client import get_http_client

session_cm = MagicMock()
session_cm.__aenter__ = AsyncMock(return_value=session)
session_cm.__aexit__ = AsyncMock(return_value=False)
return session_cm
return patch.object(get_http_client(), "tracked_request", return_value=upstream_cm)


# ---------------------------------------------------------------------------
Expand All @@ -85,13 +87,13 @@ class TestHappyPath:

def test_returns_200(self, client: TestClient):
upstream_cm = _mock_upstream(200, _SDP_ANSWER)
session_cm = _mock_session(upstream_cm)
http_client_patch = _patch_http_client(upstream_cm)

with (
patch(
"voice_processing.realtime.openai_provider.OpenAIRealtimeProvider._api_key", return_value="sk-test-key"
),
patch("aiohttp.ClientSession", return_value=session_cm),
http_client_patch,
):
resp = client.post(
"/api/voice/realtime/session",
Expand All @@ -102,13 +104,13 @@ def test_returns_200(self, client: TestClient):

def test_returns_sdp_content_type(self, client: TestClient):
upstream_cm = _mock_upstream(200, _SDP_ANSWER)
session_cm = _mock_session(upstream_cm)
http_client_patch = _patch_http_client(upstream_cm)

with (
patch(
"voice_processing.realtime.openai_provider.OpenAIRealtimeProvider._api_key", return_value="sk-test-key"
),
patch("aiohttp.ClientSession", return_value=session_cm),
http_client_patch,
):
resp = client.post(
"/api/voice/realtime/session",
Expand All @@ -119,13 +121,13 @@ def test_returns_sdp_content_type(self, client: TestClient):

def test_returns_upstream_body(self, client: TestClient):
upstream_cm = _mock_upstream(200, _SDP_ANSWER)
session_cm = _mock_session(upstream_cm)
http_client_patch = _patch_http_client(upstream_cm)

with (
patch(
"voice_processing.realtime.openai_provider.OpenAIRealtimeProvider._api_key", return_value="sk-test-key"
),
patch("aiohttp.ClientSession", return_value=session_cm),
http_client_patch,
):
resp = client.post(
"/api/voice/realtime/session",
Expand All @@ -136,13 +138,13 @@ def test_returns_upstream_body(self, client: TestClient):

def test_accepts_201_from_upstream(self, client: TestClient):
upstream_cm = _mock_upstream(201, _SDP_ANSWER)
session_cm = _mock_session(upstream_cm)
http_client_patch = _patch_http_client(upstream_cm)

with (
patch(
"voice_processing.realtime.openai_provider.OpenAIRealtimeProvider._api_key", return_value="sk-test-key"
),
patch("aiohttp.ClientSession", return_value=session_cm),
http_client_patch,
):
resp = client.post(
"/api/voice/realtime/session",
Expand Down Expand Up @@ -190,13 +192,13 @@ class TestUpstream401:

def test_upstream_401_returns_502(self, client: TestClient):
upstream_cm = _mock_upstream(401, b'{"error":"unauthorized"}')
session_cm = _mock_session(upstream_cm)
http_client_patch = _patch_http_client(upstream_cm)

with (
patch(
"voice_processing.realtime.openai_provider.OpenAIRealtimeProvider._api_key", return_value="sk-bad-key"
),
patch("aiohttp.ClientSession", return_value=session_cm),
http_client_patch,
):
resp = client.post(
"/api/voice/realtime/session",
Expand All @@ -207,13 +209,13 @@ def test_upstream_401_returns_502(self, client: TestClient):

def test_upstream_401_error_body(self, client: TestClient):
upstream_cm = _mock_upstream(401, b'{"error":"unauthorized"}')
session_cm = _mock_session(upstream_cm)
http_client_patch = _patch_http_client(upstream_cm)

with (
patch(
"voice_processing.realtime.openai_provider.OpenAIRealtimeProvider._api_key", return_value="sk-bad-key"
),
patch("aiohttp.ClientSession", return_value=session_cm),
http_client_patch,
):
resp = client.post(
"/api/voice/realtime/session",
Expand All @@ -235,13 +237,13 @@ class TestUpstream5xx:
@pytest.mark.parametrize("status", [500, 502, 503, 504])
def test_upstream_5xx_returns_502(self, client: TestClient, status: int):
upstream_cm = _mock_upstream(status, b"internal server error")
session_cm = _mock_session(upstream_cm)
http_client_patch = _patch_http_client(upstream_cm)

with (
patch(
"voice_processing.realtime.openai_provider.OpenAIRealtimeProvider._api_key", return_value="sk-test-key"
),
patch("aiohttp.ClientSession", return_value=session_cm),
http_client_patch,
):
resp = client.post(
"/api/voice/realtime/session",
Expand All @@ -252,13 +254,13 @@ def test_upstream_5xx_returns_502(self, client: TestClient, status: int):

def test_upstream_5xx_error_body(self, client: TestClient):
upstream_cm = _mock_upstream(500, b"oops")
session_cm = _mock_session(upstream_cm)
http_client_patch = _patch_http_client(upstream_cm)

with (
patch(
"voice_processing.realtime.openai_provider.OpenAIRealtimeProvider._api_key", return_value="sk-test-key"
),
patch("aiohttp.ClientSession", return_value=session_cm),
http_client_patch,
):
resp = client.post(
"/api/voice/realtime/session",
Expand All @@ -279,13 +281,13 @@ class TestProviderDispatch:

def test_session_header_advertises_provider(self, client: TestClient):
upstream_cm = _mock_upstream(200, _SDP_ANSWER)
session_cm = _mock_session(upstream_cm)
http_client_patch = _patch_http_client(upstream_cm)
with (
patch(
"voice_processing.realtime.openai_provider.OpenAIRealtimeProvider._api_key",
return_value="sk-test-key",
),
patch("aiohttp.ClientSession", return_value=session_cm),
http_client_patch,
):
resp = client.post(
"/api/voice/realtime/session",
Expand Down
27 changes: 15 additions & 12 deletions autobot-backend/llm_shared/mock_providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,24 +131,27 @@ async def generate(self, prompt: str, model: str | None = None) -> dict:
try:
import aiohttp

from autobot_shared.http_client import get_http_client

data = {
"model": model or self._default_model,
"messages": [{"role": "user", "content": prompt}],
"stream": False,
}

async with aiohttp.ClientSession() as session:
timeout = aiohttp.ClientTimeout(total=TIMEOUT_HTTP_LONG)
async with session.post(f"{self._ollama_url}/api/chat", json=data, timeout=timeout) as response:
if response.status != 200:
error_text = await response.text()
logger.error(
"Ollama request failed: HTTP %s - %s",
response.status,
error_text,
)
return self._create_error_response(f"Error: Ollama returned HTTP {response.status}")
result = await response.json()
timeout = aiohttp.ClientTimeout(total=TIMEOUT_HTTP_LONG)
async with get_http_client().tracked_request(
"POST", f"{self._ollama_url}/api/chat", json=data, timeout=timeout
) as response:
if response.status != 200:
error_text = await response.text()
logger.error(
"Ollama request failed: HTTP %s - %s",
response.status,
error_text,
)
return self._create_error_response(f"Error: Ollama returned HTTP {response.status}")
result = await response.json()

return self._format_ollama_response(result)

Expand Down
15 changes: 9 additions & 6 deletions autobot-backend/media/link/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,12 +222,15 @@ async def _fetch_and_parse(self, url: str, metadata: Dict) -> Dict[str, Any]:
# cert verification for known-safe internal URLs.
ssl_context = False if metadata.get("allow_self_signed") else None
try:
async with aiohttp.ClientSession(headers=headers, timeout=_DEFAULT_TIMEOUT) as session:
async with session.get(url, allow_redirects=True, ssl=ssl_context) as response:
final_url = str(response.url)
content_type = response.headers.get("Content-Type", "")
raw_html = await response.text(encoding="utf-8", errors="replace")
status = response.status
from autobot_shared.http_client import get_http_client

async with get_http_client().tracked_request(
"GET", url, headers=headers, timeout=_DEFAULT_TIMEOUT, allow_redirects=True, ssl=ssl_context
) as response:
final_url = str(response.url)
content_type = response.headers.get("Content-Type", "")
raw_html = await response.text(encoding="utf-8", errors="replace")
status = response.status

if status >= 400:
return self._error_result(
Expand Down
Loading
Loading