""" Tests for gitignore filtering functionality. """ import tempfile from pathlib import Path import pytest from src.domains.tools.file.glob import GlobFilesTool from src.domains.tools.gitignore import GitignoreFilter, filter_gitignored from src.domains.tools.search.grep import GrepContentTool class TestGitignoreFilter: """Tests for GitignoreFilter class.""" @pytest.fixture def temp_dir_with_gitignore(self): """Create a temporary directory with a .gitignore file.""" with tempfile.TemporaryDirectory() as tmpdir: path = Path(tmpdir) # Create .gitignore (path / ".gitignore").write_text(""" # Python artifacts *.pyc __pycache__/ # Virtual environments .venv/ venv/ # IDE .idea/ # Custom patterns ignored_file.txt ignored_dir/ """) # Create various files and directories (path / "main.py").write_text("# Main file") (path / "test.pyc").write_bytes(b"compiled") (path / "ignored_file.txt").write_text("should be ignored") (path / "not_ignored.txt").write_text("should be visible") # Create directories (path / "__pycache__").mkdir() (path / "__pycache__" / "module.cpython-312.pyc").write_bytes(b"cache") (path / ".venv").mkdir() (path / ".venv" / "lib").mkdir(parents=True) (path / ".venv" / "lib" / "python.py").write_text("venv file") (path / "ignored_dir").mkdir() (path / "ignored_dir" / "hidden.py").write_text("hidden") (path / "src").mkdir() (path / "src" / "app.py").write_text("# App") (path / "src" / "utils.py").write_text("# Utils") yield path def test_filter_ignores_pyc_files(self, temp_dir_with_gitignore): """Test that .pyc files are ignored.""" filter_instance = GitignoreFilter(temp_dir_with_gitignore) assert filter_instance.is_ignored(temp_dir_with_gitignore / "test.pyc") assert not filter_instance.is_ignored(temp_dir_with_gitignore / "main.py") def test_filter_ignores_pycache_dir(self, temp_dir_with_gitignore): """Test that __pycache__ directory is ignored.""" filter_instance = GitignoreFilter(temp_dir_with_gitignore) pycache = temp_dir_with_gitignore / "__pycache__" assert filter_instance.is_ignored(pycache) assert filter_instance.is_ignored(pycache / "module.cpython-312.pyc") def test_filter_ignores_venv(self, temp_dir_with_gitignore): """Test that .venv directory is ignored.""" filter_instance = GitignoreFilter(temp_dir_with_gitignore) venv = temp_dir_with_gitignore / ".venv" assert filter_instance.is_ignored(venv) assert filter_instance.is_ignored(venv / "lib" / "python.py") def test_filter_ignores_custom_patterns(self, temp_dir_with_gitignore): """Test that custom gitignore patterns work.""" filter_instance = GitignoreFilter(temp_dir_with_gitignore) assert filter_instance.is_ignored(temp_dir_with_gitignore / "ignored_file.txt") assert filter_instance.is_ignored(temp_dir_with_gitignore / "ignored_dir") assert filter_instance.is_ignored(temp_dir_with_gitignore / "ignored_dir" / "hidden.py") def test_filter_allows_regular_files(self, temp_dir_with_gitignore): """Test that regular files are not ignored.""" filter_instance = GitignoreFilter(temp_dir_with_gitignore) assert not filter_instance.is_ignored(temp_dir_with_gitignore / "main.py") assert not filter_instance.is_ignored(temp_dir_with_gitignore / "not_ignored.txt") assert not filter_instance.is_ignored(temp_dir_with_gitignore / "src" / "app.py") def test_filter_paths_function(self, temp_dir_with_gitignore): """Test the filter_paths helper function.""" filter_instance = GitignoreFilter(temp_dir_with_gitignore) paths = [ temp_dir_with_gitignore / "main.py", temp_dir_with_gitignore / "test.pyc", temp_dir_with_gitignore / "src" / "app.py", temp_dir_with_gitignore / ".venv" / "lib" / "python.py", ] filtered = filter_instance.filter_paths(paths) assert len(filtered) == 2 assert temp_dir_with_gitignore / "main.py" in filtered assert temp_dir_with_gitignore / "src" / "app.py" in filtered assert temp_dir_with_gitignore / "test.pyc" not in filtered assert temp_dir_with_gitignore / ".venv" / "lib" / "python.py" not in filtered def test_filter_gitignored_convenience(self, temp_dir_with_gitignore): """Test the filter_gitignored convenience function.""" paths = list(temp_dir_with_gitignore.rglob("*.py")) filtered = filter_gitignored(paths, temp_dir_with_gitignore) # Should only include main.py, src/app.py, src/utils.py # Should exclude .venv/lib/python.py, ignored_dir/hidden.py filenames = {p.name for p in filtered} assert "main.py" in filenames assert "app.py" in filenames assert "utils.py" in filenames # Check that ignored files are not present ignored_paths = [str(p) for p in filtered] assert not any(".venv" in p for p in ignored_paths) assert not any("ignored_dir" in p for p in ignored_paths) class TestGlobWithGitignore: """Tests for GlobFilesTool gitignore integration.""" @pytest.fixture def temp_dir(self): """Create a directory with ignored and non-ignored files.""" with tempfile.TemporaryDirectory() as tmpdir: path = Path(tmpdir) # Create .gitignore (path / ".gitignore").write_text(""" *.log build/ """) # Create files (path / "main.py").write_text("# Main") (path / "debug.log").write_text("log content") (path / "src").mkdir() (path / "src" / "app.py").write_text("# App") # Create .venv (default ignored) (path / ".venv").mkdir() (path / ".venv" / "script.py").write_text("venv") # Create build directory (gitignore pattern) (path / "build").mkdir() (path / "build" / "output.py").write_text("build") yield path @pytest.mark.anyio async def test_glob_honors_gitignore_by_default(self, temp_dir): """Test that glob filters gitignored files by default.""" tool = GlobFilesTool() result = await tool.execute(pattern="**/*.py", path=str(temp_dir)) assert result.success assert "main.py" in result.data assert "app.py" in result.data assert ".venv" not in result.data assert "build" not in result.data @pytest.mark.anyio async def test_glob_filters_log_files(self, temp_dir): """Test that custom gitignore patterns (*.log) work.""" tool = GlobFilesTool() result = await tool.execute(pattern="**/*", path=str(temp_dir)) assert result.success assert "debug.log" not in result.data @pytest.mark.anyio async def test_glob_can_disable_gitignore(self, temp_dir): """Test that gitignore filtering can be disabled.""" tool = GlobFilesTool(honor_gitignore=False) result = await tool.execute(pattern="**/*.py", path=str(temp_dir)) assert result.success # When disabled, should include ignored files assert ".venv" in result.data or "build" in result.data @pytest.mark.anyio async def test_glob_override_per_call(self, temp_dir): """Test per-call gitignore override.""" tool = GlobFilesTool(honor_gitignore=True) # Default behavior - filters result1 = await tool.execute(pattern="**/*.py", path=str(temp_dir)) assert ".venv" not in result1.data # Override to disable result2 = await tool.execute( pattern="**/*.py", path=str(temp_dir), honor_gitignore=False ) assert ".venv" in result2.data or "build" in result2.data class TestGrepWithGitignore: """Tests for GrepContentTool gitignore integration.""" @pytest.fixture def temp_dir(self): """Create a directory with searchable content in ignored and non-ignored files.""" with tempfile.TemporaryDirectory() as tmpdir: path = Path(tmpdir) # Create .gitignore (path / ".gitignore").write_text(""" ignored/ """) # Create files with searchable content (path / "main.py").write_text("def search_target(): pass") (path / "src").mkdir() (path / "src" / "utils.py").write_text("def search_target(): # utils") # Create .venv with matching content (default ignored) (path / ".venv").mkdir() (path / ".venv" / "site.py").write_text("def search_target(): # venv") # Create ignored dir with matching content (path / "ignored").mkdir() (path / "ignored" / "hidden.py").write_text("def search_target(): # hidden") yield path @pytest.mark.anyio async def test_grep_honors_gitignore_by_default(self, temp_dir): """Test that grep filters gitignored files by default.""" tool = GrepContentTool() result = await tool.execute(pattern="search_target", path=str(temp_dir)) assert result.success assert "main.py" in result.data assert "utils.py" in result.data assert ".venv" not in result.data assert "ignored" not in result.data @pytest.mark.anyio async def test_grep_can_disable_gitignore(self, temp_dir): """Test that gitignore filtering can be disabled.""" tool = GrepContentTool(honor_gitignore=False) result = await tool.execute(pattern="search_target", path=str(temp_dir)) assert result.success # When disabled, should include ignored files assert ".venv" in result.data or "ignored" in result.data @pytest.mark.anyio async def test_grep_override_per_call(self, temp_dir): """Test per-call gitignore override.""" tool = GrepContentTool(honor_gitignore=True) # Default behavior - filters result1 = await tool.execute(pattern="search_target", path=str(temp_dir)) assert ".venv" not in result1.data # Override to disable result2 = await tool.execute( pattern="search_target", path=str(temp_dir), honor_gitignore=False ) assert ".venv" in result2.data or "ignored" in result2.data class TestDefaultIgnores: """Tests for default ignore patterns (no .gitignore file).""" @pytest.fixture def temp_dir_no_gitignore(self): """Create a directory without .gitignore but with common ignored dirs.""" with tempfile.TemporaryDirectory() as tmpdir: path = Path(tmpdir) # Create files (path / "main.py").write_text("# Main") # Create commonly ignored directories (path / ".venv").mkdir() (path / ".venv" / "script.py").write_text("venv") (path / "__pycache__").mkdir() (path / "__pycache__" / "cache.pyc").write_bytes(b"cache") (path / "node_modules").mkdir() (path / "node_modules" / "package.js").write_text("js") yield path @pytest.mark.anyio async def test_glob_ignores_defaults_without_gitignore(self, temp_dir_no_gitignore): """Test that default ignores work even without .gitignore.""" tool = GlobFilesTool() result = await tool.execute(pattern="**/*", path=str(temp_dir_no_gitignore)) assert result.success assert "main.py" in result.data assert ".venv" not in result.data assert "__pycache__" not in result.data assert "node_modules" not in result.data