intelligence-router/tests/test_router_sse_progress.py

68 lines
2.0 KiB
Python
Raw Normal View History

Epic: Model Switching via Sidecar — Issues #4-#7 + #8 deployment Issue #4: Automatic model detection and switch - Router extracts model from chat body, queries sidecar, triggers switch on mismatch - Matching active model routes directly to Main PC - No active model triggers cold start switch - Tests: 4 test_router_model_detection.py Issue #5: SSE switch progress feedback - _sse_format() correctly serializes SSE events - sse_progress_stream() generates phase progression events - Proxy yields SSE events then actual response - Tests: 3 test_router_sse_progress.py Issue #6: Circuit breaker + OpenRouter fallback - Circuit tracks Sidecar failures, opens after MAX_RECOVERY_ATTEMPTS (3) - OpenRouter API key from env, no longer uses x-intelligence-level header - Fixes: OPENROUTER_BASE, SSE format, circuit state isolation - Tests: 7 test_router_circuit_breaker.py Issue #7: LXC fallback chain completion - Full fallback: Main PC → OpenRouter → LXC - Each backend health-checked via /v1/models before routing - All backends down → 503 response - Fixed: execute() wrapped in try/except to trigger fallback chain - Tests: 3 test_router_fallback_lxc.py Issue #8: Systemd service deployment - deploy/llm-sidecar.service: systemd unit with Restart=always - deploy/manifest.yaml: example manifest with 3 profiles - deploy/README.md: deployment instructions - Updated: docker-compose.yml, requirements.txt, Dockerfile Test framework improvements: - tests/conftest.py: shared URL patches for all router tests - Fixed global state pollution in circuit breaker tests - Fixed test sidecar switch test (AsyncMock for async function) Total: 42 tests passing
2026-06-15 04:13:36 +03:00
"""Tests for SSE switch progress feedback — Issue #5.
SSE events emitted during model switch, phase progression visible.
"""
import asyncio
import json
import pytest
from unittest.mock import patch
from httpx import Response, ASGITransport, AsyncClient
from main import app as router_app
SIDECAR_URL = "http://localhost:8080"
Epic: Model Switching via Sidecar — Issues #4-#7 + #8 deployment Issue #4: Automatic model detection and switch - Router extracts model from chat body, queries sidecar, triggers switch on mismatch - Matching active model routes directly to Main PC - No active model triggers cold start switch - Tests: 4 test_router_model_detection.py Issue #5: SSE switch progress feedback - _sse_format() correctly serializes SSE events - sse_progress_stream() generates phase progression events - Proxy yields SSE events then actual response - Tests: 3 test_router_sse_progress.py Issue #6: Circuit breaker + OpenRouter fallback - Circuit tracks Sidecar failures, opens after MAX_RECOVERY_ATTEMPTS (3) - OpenRouter API key from env, no longer uses x-intelligence-level header - Fixes: OPENROUTER_BASE, SSE format, circuit state isolation - Tests: 7 test_router_circuit_breaker.py Issue #7: LXC fallback chain completion - Full fallback: Main PC → OpenRouter → LXC - Each backend health-checked via /v1/models before routing - All backends down → 503 response - Fixed: execute() wrapped in try/except to trigger fallback chain - Tests: 3 test_router_fallback_lxc.py Issue #8: Systemd service deployment - deploy/llm-sidecar.service: systemd unit with Restart=always - deploy/manifest.yaml: example manifest with 3 profiles - deploy/README.md: deployment instructions - Updated: docker-compose.yml, requirements.txt, Dockerfile Test framework improvements: - tests/conftest.py: shared URL patches for all router tests - Fixed global state pollution in circuit breaker tests - Fixed test sidecar switch test (AsyncMock for async function) Total: 42 tests passing
2026-06-15 04:13:36 +03:00
MAIN_PC_URL = "http://localhost:8080"
FALLBACK_URL = "http://localhost:9999"
def test_sse_format():
"""SSE events are properly formatted."""
from main import _sse_format
event = _sse_format("model_switching", {"phase": "stopping", "message": "Stopping..."})
assert "event: model_switching" in event
assert '"phase": "stopping"' in event
assert '"message": "Stopping..."' in event
def test_sse_progress_stream_yields_events():
"""SSE progress stream yields events during switch."""
from main import sse_progress_stream
async def run_test():
event = asyncio.Event() # Not set — simulates ongoing switch
events = []
async for sse_chunk in sse_progress_stream(event):
events.append(sse_chunk)
# Stop after a few events to avoid long waits
if len(events) >= 4:
break
assert len(events) >= 2
# Verify events are SSE-formatted
for sse in events:
assert "event: model_switching" in sse
asyncio.run(run_test())
def test_sse_progress_stream_completes_on_set():
"""SSE stream yields completion event when switch finishes."""
from main import sse_progress_stream
async def run_test():
event = asyncio.Event()
event.set() # Already complete
chunks = []
async for sse_chunk in sse_progress_stream(event):
chunks.append(sse_chunk)
if len(chunks) >= 5:
break
assert len(chunks) >= 1
# Should include completion event
has_complete = any('"phase": "complete"' in c for c in chunks)
assert has_complete
asyncio.run(run_test())