KI-Konzil/backend/tests/test_tools.py
Claude 7becffcc89
Fix React hook anti-patterns, incorrect mock paths, and unused code
- Replace useState+useEffect sync pattern with useMemo in
  EdgeSettingsPanel.tsx to eliminate cascading re-renders
- Remove redundant draft state in NodeSettingsPanel.tsx and use
  store data directly, eliminating useEffect sync loop
- Fix mock paths in test_tools.py: patch tavily.TavilyClient and
  pypdf.PdfReader at their source modules (lazy imports)
- Remove unused variable assignment in routes.py (god mode reject)
- Remove unused node_lookup dicts in dynamic_graph_builder.py
- Remove unused imports across test files (Blueprint, CouncilState,
  pytest, llm assignments)
- Remove unused CouncilBlueprint type import in types.test.ts
- Run npm audit fix to resolve moderate vulnerability

All 107 backend tests and 26 frontend tests pass. Ruff, ESLint,
and TypeScript checks are clean.

https://claude.ai/code/session_01XqzyT6fhS8sUe9P5fCmuVU
2026-02-22 10:29:54 +00:00

169 lines
5.8 KiB
Python

"""
Tests for agent tools (web search and PDF reader).
All external API calls are mocked — no real calls to Tavily or ChromaDB.
"""
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from unittest.mock import patch, MagicMock
class TestWebSearchTool:
"""Tests for the Tavily web search tool."""
@patch.dict(os.environ, {"TAVILY_API_KEY": ""}, clear=False)
def test_web_search_returns_error_without_api_key(self):
from tools.web_search import web_search
result = web_search.invoke({"query": "test query"})
assert "TAVILY_API_KEY" in result
@patch.dict(os.environ, {"TAVILY_API_KEY": "test-key"}, clear=False)
@patch("tavily.TavilyClient")
def test_web_search_returns_formatted_results(self, mock_client_cls):
mock_client = MagicMock()
mock_client.search.return_value = {
"results": [
{
"title": "Test Result",
"url": "https://example.com",
"content": "Some content here",
}
]
}
mock_client_cls.return_value = mock_client
from tools.web_search import web_search
result = web_search.invoke({"query": "test query"})
assert "Test Result" in result
assert "https://example.com" in result
assert "Some content here" in result
@patch.dict(os.environ, {"TAVILY_API_KEY": "test-key"}, clear=False)
@patch("tavily.TavilyClient")
def test_web_search_handles_empty_results(self, mock_client_cls):
mock_client = MagicMock()
mock_client.search.return_value = {"results": []}
mock_client_cls.return_value = mock_client
from tools.web_search import web_search
result = web_search.invoke({"query": "obscure query"})
assert "No results" in result
@patch.dict(os.environ, {"TAVILY_API_KEY": "test-key"}, clear=False)
@patch("tavily.TavilyClient")
def test_web_search_handles_api_error(self, mock_client_cls):
mock_client = MagicMock()
mock_client.search.side_effect = Exception("API rate limit")
mock_client_cls.return_value = mock_client
from tools.web_search import web_search
result = web_search.invoke({"query": "test"})
assert "Error" in result
assert "rate limit" in result
class TestCreateWebSearchTool:
"""Tests for the web search tool factory."""
@patch.dict(os.environ, {"TAVILY_API_KEY": "test-key"}, clear=False)
def test_factory_returns_tool_when_key_set(self):
from tools.web_search import create_web_search_tool
tool = create_web_search_tool()
assert tool is not None
@patch.dict(os.environ, {}, clear=True)
def test_factory_returns_none_when_key_missing(self):
from tools.web_search import create_web_search_tool
tool = create_web_search_tool()
assert tool is None
class TestPdfSearchTool:
"""Tests for the PDF reader tool."""
@patch("tools.pdf_reader._get_chroma_collection")
def test_pdf_search_empty_collection(self, mock_get_collection):
mock_collection = MagicMock()
mock_collection.count.return_value = 0
mock_get_collection.return_value = mock_collection
from tools.pdf_reader import pdf_search
result = pdf_search.invoke({"query": "test query"})
assert "No documents" in result
@patch("tools.pdf_reader._get_chroma_collection")
def test_pdf_search_returns_results(self, mock_get_collection):
mock_collection = MagicMock()
mock_collection.count.return_value = 3
mock_collection.query.return_value = {
"documents": [["First passage about AI.", "Second passage about ML."]],
"metadatas": [[
{"source": "paper.pdf", "page": 1},
{"source": "paper.pdf", "page": 3},
]],
}
mock_get_collection.return_value = mock_collection
from tools.pdf_reader import pdf_search
result = pdf_search.invoke({"query": "AI concepts"})
assert "paper.pdf" in result
assert "First passage" in result
assert "Page 1" in result
@patch("tools.pdf_reader._get_chroma_collection")
def test_pdf_search_handles_error(self, mock_get_collection):
mock_get_collection.side_effect = Exception("ChromaDB unavailable")
from tools.pdf_reader import pdf_search
result = pdf_search.invoke({"query": "test"})
assert "Error" in result
class TestPdfIngestion:
"""Tests for PDF ingestion into ChromaDB."""
@patch("tools.pdf_reader._get_chroma_collection")
@patch("pypdf.PdfReader")
def test_ingest_pdf_processes_pages(self, mock_pdf_reader_cls, mock_get_collection):
# Mock PDF with 2 pages of text
mock_page1 = MagicMock()
mock_page1.extract_text.return_value = "This is the first page with some content " * 20
mock_page2 = MagicMock()
mock_page2.extract_text.return_value = "Second page about machine learning " * 20
mock_reader = MagicMock()
mock_reader.pages = [mock_page1, mock_page2]
mock_pdf_reader_cls.return_value = mock_reader
mock_collection = MagicMock()
mock_get_collection.return_value = mock_collection
from tools.pdf_reader import ingest_pdf
chunks = ingest_pdf("/tmp/test.pdf")
assert chunks > 0
mock_collection.upsert.assert_called_once()
@patch("tools.pdf_reader._get_chroma_collection")
@patch("pypdf.PdfReader")
def test_ingest_pdf_empty_file(self, mock_pdf_reader_cls, mock_get_collection):
mock_reader = MagicMock()
mock_reader.pages = []
mock_pdf_reader_cls.return_value = mock_reader
from tools.pdf_reader import ingest_pdf
chunks = ingest_pdf("/tmp/empty.pdf")
assert chunks == 0