""" Tests for Knowledge Base Tool """ import pytest import os import sys sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from services.router.tool_manager import ToolManager, ToolResult class TestKnowledgeBaseTool: """Test KB tool functionality""" @pytest.mark.asyncio async def test_search_requires_query(self): """Test that search requires query""" tool_mgr = ToolManager({}) result = await tool_mgr._kb_tool({ "action": "search", "params": {} }) assert result.success is False assert "query" in result.error.lower() @pytest.mark.asyncio async def test_search_returns_results(self): """Test search returns results structure""" tool_mgr = ToolManager({}) result = await tool_mgr._kb_tool({ "action": "search", "params": { "query": "test query", "limit": 10 } }) assert result.success is True assert "results" in result.result assert "summary" in result.result @pytest.mark.asyncio async def test_search_with_paths_filter(self): """Test search with paths filter""" tool_mgr = ToolManager({}) result = await tool_mgr._kb_tool({ "action": "search", "params": { "query": "test", "paths": ["docs"], "limit": 5 } }) assert result.success is True assert result.result["count"] >= 0 @pytest.mark.asyncio async def test_snippets_requires_query(self): """Test snippets requires query""" tool_mgr = ToolManager({}) result = await tool_mgr._kb_tool({ "action": "snippets", "params": {} }) assert result.success is False assert "query" in result.error.lower() @pytest.mark.asyncio async def test_snippets_returns_structure(self): """Test snippets returns proper structure""" tool_mgr = ToolManager({}) result = await tool_mgr._kb_tool({ "action": "snippets", "params": { "query": "test", "limit": 5 } }) assert result.success is True assert "results" in result.result @pytest.mark.asyncio async def test_open_requires_path(self): """Test open requires path""" tool_mgr = ToolManager({}) result = await tool_mgr._kb_tool({ "action": "open", "params": {} }) assert result.success is False assert "path" in result.error.lower() @pytest.mark.asyncio async def test_open_blocks_traversal(self): """Test open blocks path traversal""" tool_mgr = ToolManager({}) result = await tool_mgr._kb_tool({ "action": "open", "params": { "path": "../../../etc/passwd" } }) assert result.success is False assert "traversal" in result.error.lower() @pytest.mark.asyncio async def test_open_blocks_not_allowed_path(self): """Test open blocks not allowed paths""" tool_mgr = ToolManager({}) result = await tool_mgr._kb_tool({ "action": "open", "params": { "path": "services/router/tool_manager.py" } }) assert result.success is False assert "not in allowed" in result.error.lower() or "not found" in result.error.lower() @pytest.mark.asyncio async def test_sources_returns_indexed(self): """Test sources returns indexed sources""" tool_mgr = ToolManager({}) result = await tool_mgr._kb_tool({ "action": "sources", "params": {} }) assert result.success is True assert "sources" in result.result assert "allowed_paths" in result.result @pytest.mark.asyncio async def test_sources_with_paths_filter(self): """Test sources with paths filter""" tool_mgr = ToolManager({}) result = await tool_mgr._kb_tool({ "action": "sources", "params": { "paths": ["docs"] } }) assert result.success is True @pytest.mark.asyncio async def test_unknown_action_returns_error(self): """Test unknown action returns error""" tool_mgr = ToolManager({}) result = await tool_mgr._kb_tool({ "action": "unknown_action", "params": {} }) assert result.success is False assert "unknown action" in result.error.lower() @pytest.mark.asyncio async def test_search_with_file_glob(self): """Test search with file glob filter""" tool_mgr = ToolManager({}) result = await tool_mgr._kb_tool({ "action": "search", "params": { "query": "test", "file_glob": "**/*.md", "limit": 10 } }) assert result.success is True @pytest.mark.asyncio async def test_snippets_context_lines(self): """Test snippets with context lines""" tool_mgr = ToolManager({}) result = await tool_mgr._kb_tool({ "action": "snippets", "params": { "query": "test", "context_lines": 2, "limit": 3 } }) assert result.success is True @pytest.mark.asyncio async def test_open_with_line_range(self): """Test open with line range""" tool_mgr = ToolManager({}) result = await tool_mgr._kb_tool({ "action": "open", "params": { "path": "docs/README.md", "start_line": 1, "end_line": 10 } }) if result.success: assert "content" in result.result assert "start_line" in result.result @pytest.mark.asyncio async def test_search_result_structure(self): """Test search result has proper structure""" tool_mgr = ToolManager({}) result = await tool_mgr._kb_tool({ "action": "search", "params": { "query": "documentation", "limit": 5 } }) assert result.success is True if result.result["count"] > 0: r = result.result["results"][0] assert "path" in r assert "score" in r assert "highlights" in r @pytest.mark.asyncio async def test_redaction_of_secrets(self): """Test that secrets are redacted""" tool_mgr = ToolManager({}) result = await tool_mgr._kb_tool({ "action": "snippets", "params": { "query": "API_KEY", "limit": 1 } }) if result.success and result.result["count"] > 0: for snippet in result.result["results"]: text = snippet.get("text", "") assert "sk-***" in text or "API_KEY" not in text or "***" in text