Initial commit: Research Bridge API with Podman support

This commit is contained in:
Henry
2026-03-14 12:45:36 +00:00
commit 1130305e71
29 changed files with 2451 additions and 0 deletions

View File

@@ -0,0 +1,185 @@
"""Unit tests for Synthesizer."""
from unittest.mock import AsyncMock, Mock, patch
import pytest
from src.llm.synthesizer import Synthesizer, SynthesizerError
from src.models.schemas import SearchResult
class TestSynthesizer:
"""Test cases for Synthesizer."""
@pytest.fixture
def synthesizer(self):
return Synthesizer(api_key="sk-test-key")
def test_init_without_api_key_raises(self):
"""Test that initialization without API key raises error."""
with patch.dict("os.environ", {}, clear=True):
with pytest.raises(SynthesizerError) as exc_info:
Synthesizer()
assert "API key required" in str(exc_info.value)
def test_init_with_env_var(self):
"""Test initialization with environment variable."""
with patch.dict("os.environ", {"RESEARCH_BRIDGE_KIMI_API_KEY": "sk-env-key"}):
synth = Synthesizer()
assert synth.api_key == "sk-env-key"
def test_default_headers_set(self):
"""Test that required User-Agent header is set."""
synth = Synthesizer(api_key="sk-test")
assert "User-Agent" in synth.DEFAULT_HEADERS
assert synth.DEFAULT_HEADERS["User-Agent"] == "KimiCLI/0.77"
def test_format_search_results(self, synthesizer):
"""Test formatting of search results."""
results = [
SearchResult(
title="Test Title",
url="https://example.com",
content="Test content",
source="google"
),
SearchResult(
title="Second Title",
url="https://test.com",
content=None,
source="bing"
)
]
formatted = synthesizer._format_search_results(results)
assert "[1] Test Title" in formatted
assert "URL: https://example.com" in formatted
assert "Test content" in formatted
assert "[2] Second Title" in formatted
assert "No snippet available" in formatted
def test_build_prompt(self, synthesizer):
"""Test prompt building."""
results = [
SearchResult(
title="Python Asyncio",
url="https://docs.python.org",
content="Asyncio docs",
source="google"
)
]
prompt = synthesizer._build_prompt("what is asyncio", results)
assert "User Query: what is asyncio" in prompt
assert "Python Asyncio" in prompt
assert "docs.python.org" in prompt
@pytest.mark.asyncio
async def test_synthesize_success(self, synthesizer):
"""Test successful synthesis."""
mock_response = Mock()
mock_response.choices = [Mock()]
mock_response.choices[0].message.content = "Asyncio is a library..."
mock_response.usage = Mock()
mock_response.usage.total_tokens = 100
mock_response.usage.prompt_tokens = 80
mock_response.usage.completion_tokens = 20
mock_client = AsyncMock()
mock_client.chat.completions.create = AsyncMock(return_value=mock_response)
with patch.object(synthesizer, '_client', mock_client):
results = [
SearchResult(
title="Test",
url="https://example.com",
content="Content",
source="google"
)
]
result = await synthesizer.synthesize("test query", results)
assert result.content == "Asyncio is a library..."
assert result.tokens_used == 100
assert result.prompt_tokens == 80
assert result.completion_tokens == 20
assert len(result.sources) == 1
@pytest.mark.asyncio
async def test_synthesize_truncates_results(self, synthesizer):
"""Test that synthesis truncates to top 5 results."""
mock_response = Mock()
mock_response.choices = [Mock()]
mock_response.choices[0].message.content = "Answer"
mock_response.usage = None
mock_client = AsyncMock()
mock_client.chat.completions.create = AsyncMock(return_value=mock_response)
# Create 10 results
results = [
SearchResult(
title=f"Result {i}",
url=f"https://example{i}.com",
content=f"Content {i}",
source="google"
)
for i in range(10)
]
with patch.object(synthesizer, '_client', mock_client):
result = await synthesizer.synthesize("test", results)
# Should only use first 5
assert len(result.sources) == 5
@pytest.mark.asyncio
async def test_synthesize_api_error(self, synthesizer):
"""Test handling of API errors."""
mock_client = AsyncMock()
mock_client.chat.completions.create = AsyncMock(
side_effect=Exception("API Error")
)
with patch.object(synthesizer, '_client', mock_client):
results = [
SearchResult(
title="Test",
url="https://example.com",
content="Content",
source="google"
)
]
with pytest.raises(SynthesizerError) as exc_info:
await synthesizer.synthesize("test", results)
assert "Kimi API error" in str(exc_info.value)
@pytest.mark.asyncio
async def test_health_check_success(self, synthesizer):
"""Test successful health check."""
mock_response = Mock()
mock_response.choices = [Mock()]
mock_response.choices[0].message.content = "Hi"
mock_client = AsyncMock()
mock_client.chat.completions.create = AsyncMock(return_value=mock_response)
with patch.object(synthesizer, '_client', mock_client):
result = await synthesizer.health_check()
assert result is True
@pytest.mark.asyncio
async def test_health_check_failure(self, synthesizer):
"""Test failed health check."""
mock_client = AsyncMock()
mock_client.chat.completions.create = AsyncMock(
side_effect=Exception("Connection error")
)
with patch.object(synthesizer, '_client', mock_client):
result = await synthesizer.health_check()
assert result is False