Files
webber/webber-api/tests/test_gitignore.py
T
jpmschweitzerandClaude Opus 4.5 3b58fa4f8b
Build and Push API / release (push) Successful in 3s
Build and Push API / build (push) Successful in 2m27s
refactor: reorganize into monorepo with separate subprojects
Structure webber into three independent subprojects:
- webber-api/: FastAPI backend server with all agent code
- webber-cli/: Standalone CLI client (renamed from cli/ to webber_cli/)
- webber-sandbox/: Test project for functional testing

Key changes:
- Each subproject has its own .venv (Python 3.12+)
- Added sandbox.sh for managing test project templates
- Created sandbox-templates/ with calculator-cli and empty starter
- Updated CI/CD for prefixed tags (api/v*, cli/v*)
- Added comprehensive AGENTS.md with operational instructions
- Added gitignore filtering to glob and grep tools
- Created pyproject.toml for each subproject

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 10:37:47 +01:00

331 lines
12 KiB
Python

"""
Tests for gitignore filtering functionality.
"""
import tempfile
from pathlib import Path
import pytest
from src.domains.tools.gitignore import GitignoreFilter, filter_gitignored
from src.domains.tools.file.glob import GlobFilesTool
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