from __future__ import annotations import unittest from src.models import PortingModule from src.permissions import ToolPermissionContext from src.tool_pool import ToolPool, assemble_tool_pool class TestAssembleToolPool(unittest.TestCase): def test_returns_tool_pool_with_tools(self) -> None: pool = assemble_tool_pool() self.assertIsInstance(pool, ToolPool) self.assertIsInstance(pool.tools, tuple) self.assertTrue(all(isinstance(t, PortingModule) for t in pool.tools)) def test_default_mode_includes_tools(self) -> None: pool = assemble_tool_pool() self.assertGreater(len(pool.tools), 0) def test_simple_mode_flag_stored(self) -> None: pool_default = assemble_tool_pool() pool_simple = assemble_tool_pool(simple_mode=True) self.assertFalse(pool_default.simple_mode) self.assertTrue(pool_simple.simple_mode) def test_include_mcp_flag_stored(self) -> None: pool_default = assemble_tool_pool() pool_no_mcp = assemble_tool_pool(include_mcp=False) self.assertTrue(pool_default.include_mcp) self.assertFalse(pool_no_mcp.include_mcp) def test_simple_mode_reduces_tools(self) -> None: pool_full = assemble_tool_pool(simple_mode=False) pool_simple = assemble_tool_pool(simple_mode=True) self.assertGreater(len(pool_full.tools), len(pool_simple.tools)) simple_names = {t.name for t in pool_simple.tools} self.assertTrue(simple_names.issubset({'BashTool', 'FileReadTool', 'FileEditTool'})) def test_include_mcp_false_excludes_mcp_tools(self) -> None: pool = assemble_tool_pool(include_mcp=False) for tool in pool.tools: self.assertNotIn('mcp', tool.name.lower()) self.assertNotIn('mcp', tool.source_hint.lower()) def test_permission_context_filters_blocked_tools(self) -> None: ctx = ToolPermissionContext.from_iterables(deny_names=['BashTool']) pool = assemble_tool_pool(permission_context=ctx) tool_names = {t.name for t in pool.tools} self.assertNotIn('BashTool', tool_names) pool_unfiltered = assemble_tool_pool() unfiltered_names = {t.name for t in pool_unfiltered.tools} self.assertIn('BashTool', unfiltered_names) class TestToolPoolAsMarkdown(unittest.TestCase): def test_includes_header_and_tool_count(self) -> None: pool = assemble_tool_pool() md = pool.as_markdown() self.assertIn('# Tool Pool', md) self.assertIn(f'Tool count: {len(pool.tools)}', md) def test_includes_mode_flags(self) -> None: pool = assemble_tool_pool(simple_mode=True, include_mcp=False) md = pool.as_markdown() self.assertIn('Simple mode: True', md) self.assertIn('Include MCP: False', md) def test_shows_at_most_15_tools(self) -> None: pool = assemble_tool_pool() self.assertGreater(len(pool.tools), 15, 'Need >15 tools for this test') md = pool.as_markdown() tool_lines = [line for line in md.splitlines() if line.startswith('- ')] self.assertEqual(len(tool_lines), 15) def test_empty_tools_renders_correctly(self) -> None: pool = ToolPool(tools=(), simple_mode=False, include_mcp=True) md = pool.as_markdown() self.assertIn('# Tool Pool', md) self.assertIn('Tool count: 0', md) self.assertIn('Simple mode: False', md) self.assertIn('Include MCP: True', md) tool_lines = [line for line in md.splitlines() if line.startswith('- ')] self.assertEqual(len(tool_lines), 0) def test_tool_lines_contain_name_and_source_hint(self) -> None: tool = PortingModule( name='TestTool', responsibility='testing', source_hint='test/path.ts', ) pool = ToolPool(tools=(tool,), simple_mode=False, include_mcp=False) md = pool.as_markdown() self.assertIn('- TestTool — test/path.ts', md) class TestToolPoolFrozen(unittest.TestCase): def test_cannot_mutate_fields(self) -> None: pool = ToolPool(tools=(), simple_mode=False, include_mcp=True) with self.assertRaises(AttributeError): pool.simple_mode = True # type: ignore[misc] if __name__ == '__main__': unittest.main()