mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
e0d8aeb149
Signed-off-by: phernandez <paul@basicmachines.co> Signed-off-by: Drew Cain <groksrc@gmail.com> Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Drew Cain <groksrc@gmail.com>
316 lines
11 KiB
Python
316 lines
11 KiB
Python
"""Tests for ignore_utils module."""
|
|
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
from basic_memory.ignore_utils import (
|
|
DEFAULT_IGNORE_PATTERNS,
|
|
load_gitignore_patterns,
|
|
should_ignore_path,
|
|
filter_files,
|
|
)
|
|
|
|
|
|
def test_load_default_patterns_only():
|
|
"""Test loading default patterns when no .gitignore exists."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_path = Path(temp_dir)
|
|
patterns = load_gitignore_patterns(temp_path)
|
|
|
|
# Should include all default patterns
|
|
assert DEFAULT_IGNORE_PATTERNS.issubset(patterns)
|
|
# Should only have default patterns (no custom ones)
|
|
assert patterns == DEFAULT_IGNORE_PATTERNS
|
|
|
|
|
|
def test_load_patterns_with_gitignore():
|
|
"""Test loading patterns from .gitignore file."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_path = Path(temp_dir)
|
|
|
|
# Create a .gitignore file
|
|
gitignore_content = """
|
|
# Python
|
|
*.pyc
|
|
__pycache__/
|
|
|
|
# Node
|
|
node_modules/
|
|
*.log
|
|
|
|
# Custom
|
|
secrets/
|
|
temp_*
|
|
"""
|
|
(temp_path / ".gitignore").write_text(gitignore_content)
|
|
|
|
patterns = load_gitignore_patterns(temp_path)
|
|
|
|
# Should include default patterns
|
|
assert DEFAULT_IGNORE_PATTERNS.issubset(patterns)
|
|
|
|
# Should include custom patterns from .gitignore
|
|
assert "*.pyc" in patterns
|
|
assert "__pycache__/" in patterns
|
|
assert "node_modules/" in patterns
|
|
assert "*.log" in patterns
|
|
assert "secrets/" in patterns
|
|
assert "temp_*" in patterns
|
|
|
|
# Should skip comments and empty lines
|
|
assert "# Python" not in patterns
|
|
assert "# Node" not in patterns
|
|
assert "# Custom" not in patterns
|
|
|
|
|
|
def test_load_patterns_empty_gitignore():
|
|
"""Test loading patterns with empty .gitignore file."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_path = Path(temp_dir)
|
|
|
|
# Create empty .gitignore file
|
|
(temp_path / ".gitignore").write_text("")
|
|
|
|
patterns = load_gitignore_patterns(temp_path)
|
|
|
|
# Should only have default patterns
|
|
assert patterns == DEFAULT_IGNORE_PATTERNS
|
|
|
|
|
|
def test_load_patterns_unreadable_gitignore():
|
|
"""Test graceful handling of unreadable .gitignore file."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_path = Path(temp_dir)
|
|
|
|
# Create .gitignore file with restricted permissions
|
|
gitignore_file = temp_path / ".gitignore"
|
|
gitignore_file.write_text("*.log")
|
|
gitignore_file.chmod(0o000) # No read permissions
|
|
|
|
try:
|
|
patterns = load_gitignore_patterns(temp_path)
|
|
|
|
# On Windows, chmod might not work as expected, so we need to check
|
|
# if the file is actually unreadable
|
|
try:
|
|
with gitignore_file.open("r"):
|
|
pass
|
|
# If we can read it, the test environment doesn't support this scenario
|
|
# In this case, the patterns should include *.log
|
|
assert "*.log" in patterns
|
|
except (PermissionError, OSError):
|
|
# File is actually unreadable, should fallback to default patterns only
|
|
assert patterns == DEFAULT_IGNORE_PATTERNS
|
|
assert "*.log" not in patterns
|
|
finally:
|
|
# Restore permissions for cleanup
|
|
gitignore_file.chmod(0o644)
|
|
|
|
|
|
def test_should_ignore_default_patterns():
|
|
"""Test ignoring files matching default patterns."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_path = Path(temp_dir)
|
|
|
|
patterns = DEFAULT_IGNORE_PATTERNS
|
|
|
|
test_cases = [
|
|
# Git directory
|
|
(temp_path / ".git" / "config", True),
|
|
# Python artifacts
|
|
(temp_path / "main.pyc", True),
|
|
(temp_path / "__pycache__" / "main.cpython-39.pyc", True),
|
|
(temp_path / "src" / "__pycache__" / "module.pyc", True),
|
|
# Virtual environments
|
|
(temp_path / ".venv" / "lib" / "python.so", True),
|
|
(temp_path / "venv" / "bin" / "python", True),
|
|
(temp_path / "env" / "lib" / "site-packages", True),
|
|
# Node.js
|
|
(temp_path / "node_modules" / "package" / "index.js", True),
|
|
# IDE files
|
|
(temp_path / ".idea" / "workspace.xml", True),
|
|
(temp_path / ".vscode" / "settings.json", True),
|
|
# OS files
|
|
(temp_path / ".DS_Store", True),
|
|
(temp_path / "Thumbs.db", True),
|
|
# Valid files that should NOT be ignored
|
|
(temp_path / "main.py", False),
|
|
(temp_path / "README.md", False),
|
|
(temp_path / "src" / "module.py", False),
|
|
(temp_path / "package.json", False),
|
|
]
|
|
|
|
for file_path, should_be_ignored in test_cases:
|
|
result = should_ignore_path(file_path, temp_path, patterns)
|
|
assert result == should_be_ignored, (
|
|
f"Failed for {file_path}: expected {should_be_ignored}, got {result}"
|
|
)
|
|
|
|
|
|
def test_should_ignore_glob_patterns():
|
|
"""Test glob pattern matching."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_path = Path(temp_dir)
|
|
|
|
patterns = {"*.log", "temp_*", "test*.txt"}
|
|
|
|
test_cases = [
|
|
(temp_path / "debug.log", True),
|
|
(temp_path / "app.log", True),
|
|
(temp_path / "sub" / "error.log", True),
|
|
(temp_path / "temp_file.txt", True),
|
|
(temp_path / "temp_123", True),
|
|
(temp_path / "test_data.txt", True),
|
|
(temp_path / "testfile.txt", True),
|
|
(temp_path / "app.txt", False),
|
|
(temp_path / "file.py", False),
|
|
(temp_path / "data.json", False),
|
|
]
|
|
|
|
for file_path, should_be_ignored in test_cases:
|
|
result = should_ignore_path(file_path, temp_path, patterns)
|
|
assert result == should_be_ignored, f"Failed for {file_path}"
|
|
|
|
|
|
def test_should_ignore_directory_patterns():
|
|
"""Test directory pattern matching (ending with /)."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_path = Path(temp_dir)
|
|
|
|
patterns = {"build/", "dist/", "logs/"}
|
|
|
|
test_cases = [
|
|
(temp_path / "build" / "output.js", True),
|
|
(temp_path / "dist" / "main.css", True),
|
|
(temp_path / "logs" / "app.log", True),
|
|
(temp_path / "src" / "build" / "file.js", True), # Nested
|
|
(temp_path / "build.py", False), # File with same name
|
|
(temp_path / "build_script.sh", False), # Similar name
|
|
(temp_path / "src" / "main.py", False), # Different directory
|
|
]
|
|
|
|
for file_path, should_be_ignored in test_cases:
|
|
result = should_ignore_path(file_path, temp_path, patterns)
|
|
assert result == should_be_ignored, f"Failed for {file_path}"
|
|
|
|
|
|
def test_should_ignore_root_relative_patterns():
|
|
"""Test patterns starting with / (root relative)."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_path = Path(temp_dir)
|
|
|
|
patterns = {"/config.txt", "/build/", "/tmp/*.log"}
|
|
|
|
test_cases = [
|
|
(temp_path / "config.txt", True), # Root level
|
|
(temp_path / "build" / "app.js", True), # Root level directory
|
|
(temp_path / "tmp" / "debug.log", True), # Root level with glob
|
|
(temp_path / "src" / "config.txt", False), # Not at root
|
|
(temp_path / "project" / "build" / "file.js", False), # Not at root
|
|
(temp_path / "data" / "tmp" / "app.log", False), # Not at root
|
|
]
|
|
|
|
for file_path, should_be_ignored in test_cases:
|
|
result = should_ignore_path(file_path, temp_path, patterns)
|
|
assert result == should_be_ignored, f"Failed for {file_path}"
|
|
|
|
|
|
def test_should_ignore_invalid_relative_path():
|
|
"""Test handling of paths that cannot be made relative to base."""
|
|
patterns = {"*.pyc"}
|
|
|
|
# File outside of base path should not be ignored
|
|
base_path = Path("/tmp/project")
|
|
file_path = Path("/home/user/file.pyc")
|
|
|
|
result = should_ignore_path(file_path, base_path, patterns)
|
|
assert result is False
|
|
|
|
|
|
def test_filter_files_with_patterns():
|
|
"""Test filtering files with given patterns."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_path = Path(temp_dir)
|
|
|
|
# Create test files
|
|
files = [
|
|
temp_path / "main.py",
|
|
temp_path / "main.pyc",
|
|
temp_path / "__pycache__" / "module.pyc",
|
|
temp_path / "README.md",
|
|
temp_path / ".git" / "config",
|
|
temp_path / "package.json",
|
|
]
|
|
|
|
# Ensure parent directories exist
|
|
for file_path in files:
|
|
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
file_path.write_text("test content")
|
|
|
|
patterns = {"*.pyc", "__pycache__", ".git"}
|
|
filtered_files, ignored_count = filter_files(files, temp_path, patterns)
|
|
|
|
# Should keep valid files
|
|
expected_kept = [
|
|
temp_path / "main.py",
|
|
temp_path / "README.md",
|
|
temp_path / "package.json",
|
|
]
|
|
|
|
assert len(filtered_files) == 3
|
|
assert set(filtered_files) == set(expected_kept)
|
|
assert ignored_count == 3 # main.pyc, module.pyc, config
|
|
|
|
|
|
def test_filter_files_no_patterns():
|
|
"""Test filtering with no patterns (should keep all files)."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_path = Path(temp_dir)
|
|
|
|
files = [
|
|
temp_path / "main.py",
|
|
temp_path / "main.pyc",
|
|
temp_path / "README.md",
|
|
]
|
|
|
|
patterns = set()
|
|
filtered_files, ignored_count = filter_files(files, temp_path, patterns)
|
|
|
|
assert len(filtered_files) == 3
|
|
assert set(filtered_files) == set(files)
|
|
assert ignored_count == 0
|
|
|
|
|
|
def test_filter_files_with_gitignore_loading():
|
|
"""Test filtering with automatic .gitignore loading."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
temp_path = Path(temp_dir)
|
|
|
|
# Create .gitignore
|
|
gitignore_content = """
|
|
*.log
|
|
temp_*
|
|
"""
|
|
(temp_path / ".gitignore").write_text(gitignore_content)
|
|
|
|
# Create test files
|
|
files = [
|
|
temp_path / "app.py",
|
|
temp_path / "debug.log",
|
|
temp_path / "temp_file.txt",
|
|
temp_path / "README.md",
|
|
]
|
|
|
|
# Ensure files exist
|
|
for file_path in files:
|
|
file_path.write_text("test content")
|
|
|
|
filtered_files, ignored_count = filter_files(files, temp_path) # patterns=None
|
|
|
|
# Should ignore .log files and temp_* files, plus default patterns
|
|
expected_kept = [temp_path / "app.py", temp_path / "README.md"]
|
|
|
|
assert len(filtered_files) == 2
|
|
assert set(filtered_files) == set(expected_kept)
|
|
assert ignored_count == 2 # debug.log, temp_file.txt
|