[claude] Add unit tests for research_tools.py (#1237) (#1239)
Some checks failed
Tests / lint (push) Has been cancelled
Tests / test (push) Has been cancelled

This commit was merged in pull request #1239.
This commit is contained in:
2026-03-23 23:06:06 +00:00
parent 1e1689f931
commit b61fcd3495

View File

@@ -0,0 +1,149 @@
"""Unit tests for src/timmy/research_tools.py.
Refs #1237
"""
from __future__ import annotations
import sys
from types import ModuleType
from unittest.mock import MagicMock, patch
import pytest
pytestmark = pytest.mark.unit
# ── Stub serpapi before any import of research_tools ─────────────────────────
_serpapi_stub = ModuleType("serpapi")
_google_search_mock = MagicMock()
_serpapi_stub.GoogleSearch = _google_search_mock
sys.modules.setdefault("serpapi", _serpapi_stub)
# ── google_web_search ─────────────────────────────────────────────────────────
class TestGoogleWebSearch:
"""google_web_search returns results or degrades gracefully."""
@pytest.mark.asyncio
async def test_returns_empty_string_when_no_api_key(self, monkeypatch):
monkeypatch.delenv("SERPAPI_API_KEY", raising=False)
from timmy.research_tools import google_web_search
result = await google_web_search("test query")
assert result == ""
@pytest.mark.asyncio
async def test_logs_warning_when_no_api_key(self, monkeypatch, caplog):
import logging
monkeypatch.delenv("SERPAPI_API_KEY", raising=False)
from timmy.research_tools import google_web_search
with caplog.at_level(logging.WARNING, logger="timmy.research_tools"):
await google_web_search("test query")
assert any("SERPAPI_API_KEY" in rec.message for rec in caplog.records)
@pytest.mark.asyncio
async def test_calls_google_search_with_api_key(self, monkeypatch):
monkeypatch.setenv("SERPAPI_API_KEY", "fake-key-123")
mock_instance = MagicMock()
mock_instance.get_dict.return_value = {"organic_results": [{"title": "Result"}]}
with patch("timmy.research_tools.GoogleSearch", return_value=mock_instance) as mock_cls:
from timmy.research_tools import google_web_search
result = await google_web_search("hello world")
mock_cls.assert_called_once()
call_params = mock_cls.call_args[0][0]
assert call_params["q"] == "hello world"
assert call_params["api_key"] == "fake-key-123"
mock_instance.get_dict.assert_called_once()
assert "organic_results" in result
@pytest.mark.asyncio
async def test_returns_string_result(self, monkeypatch):
monkeypatch.setenv("SERPAPI_API_KEY", "key")
mock_instance = MagicMock()
mock_instance.get_dict.return_value = {"answer": 42}
with patch("timmy.research_tools.GoogleSearch", return_value=mock_instance):
from timmy.research_tools import google_web_search
result = await google_web_search("query")
assert isinstance(result, str)
@pytest.mark.asyncio
async def test_passes_query_to_params(self, monkeypatch):
monkeypatch.setenv("SERPAPI_API_KEY", "k")
mock_instance = MagicMock()
mock_instance.get_dict.return_value = {}
with patch("timmy.research_tools.GoogleSearch", return_value=mock_instance) as mock_cls:
from timmy.research_tools import google_web_search
await google_web_search("specific search term")
params = mock_cls.call_args[0][0]
assert params["q"] == "specific search term"
# ── get_llm_client ────────────────────────────────────────────────────────────
class TestGetLLMClient:
"""get_llm_client returns a client with a completion method."""
def test_returns_non_none_client(self):
from timmy.research_tools import get_llm_client
client = get_llm_client()
assert client is not None
def test_client_has_completion_method(self):
from timmy.research_tools import get_llm_client
client = get_llm_client()
assert hasattr(client, "completion")
assert callable(client.completion)
@pytest.mark.asyncio
async def test_completion_returns_object_with_text(self):
from timmy.research_tools import get_llm_client
client = get_llm_client()
result = await client.completion("test prompt", max_tokens=100)
assert hasattr(result, "text")
@pytest.mark.asyncio
async def test_completion_text_is_string(self):
from timmy.research_tools import get_llm_client
client = get_llm_client()
result = await client.completion("any prompt", max_tokens=50)
assert isinstance(result.text, str)
@pytest.mark.asyncio
async def test_completion_text_contains_prompt(self):
from timmy.research_tools import get_llm_client
client = get_llm_client()
result = await client.completion("my prompt", max_tokens=50)
assert "my prompt" in result.text
def test_each_call_returns_new_client(self):
from timmy.research_tools import get_llm_client
client_a = get_llm_client()
client_b = get_llm_client()
# Both should be functional clients (not necessarily the same instance)
assert hasattr(client_a, "completion")
assert hasattr(client_b, "completion")