Files
zooqueenandHanzo Dev b144973bfc fix(lint): resolve all ruff + black errors across all packages
Fix 35 ruff errors (E722, F841, B904, B905, B007, E741, S202, S310,
S306, S110, TID251, UP042, F401) and run black on 526 files.

Co-authored-by: Hanzo Dev <[email protected]>
2026-02-21 23:32:32 -08:00

378 lines
12 KiB
Python

"""Tests for Git history ingestion into vector store."""
import subprocess
import tempfile
from datetime import datetime
from pathlib import Path
import pytest
from hanzo_mcp.tools.vector.infinity_store import InfinityVectorStore
from hanzo_mcp.tools.vector.project_manager import ProjectVectorManager
class TestGitIngestion:
"""Test suite for ingesting git repositories."""
@pytest.fixture
def test_git_repo(self):
"""Create a test git repository with history."""
with tempfile.TemporaryDirectory() as tmpdir:
repo_path = Path(tmpdir)
# Initialize git repo
subprocess.run(["git", "init"], cwd=repo_path, check=True)
subprocess.run(
["git", "config", "user.email", "[email protected]"],
cwd=repo_path,
check=True,
)
subprocess.run(
["git", "config", "user.name", "Test User"], cwd=repo_path, check=True
)
# Create initial commit
readme = repo_path / "README.md"
readme.write_text(
"# Test Project\n\nThis is a test project for git ingestion."
)
subprocess.run(["git", "add", "README.md"], cwd=repo_path, check=True)
subprocess.run(
["git", "commit", "-m", "Initial commit"], cwd=repo_path, check=True
)
# Add Python file
main_py = repo_path / "main.py"
main_py.write_text('''#!/usr/bin/env python3
"""Main application file."""
def main():
"""Entry point."""
print("Hello, World!")
if __name__ == "__main__":
main()
''')
subprocess.run(["git", "add", "main.py"], cwd=repo_path, check=True)
subprocess.run(
["git", "commit", "-m", "Add main.py"], cwd=repo_path, check=True
)
# Add more files and commits
utils_py = repo_path / "utils.py"
utils_py.write_text('''"""Utility functions."""
def format_string(s):
"""Format a string."""
return s.strip().title()
def calculate_sum(numbers):
"""Calculate sum of numbers."""
return sum(numbers)
''')
subprocess.run(["git", "add", "utils.py"], cwd=repo_path, check=True)
subprocess.run(
["git", "commit", "-m", "Add utility functions"],
cwd=repo_path,
check=True,
)
# Modify existing file
main_py.write_text('''#!/usr/bin/env python3
"""Main application file."""
from utils import format_string
def main():
"""Entry point."""
message = format_string(" hello, world! ")
print(message)
def run():
"""Run the application."""
main()
if __name__ == "__main__":
run()
''')
subprocess.run(["git", "add", "main.py"], cwd=repo_path, check=True)
subprocess.run(
["git", "commit", "-m", "Update main.py to use utils"],
cwd=repo_path,
check=True,
)
# Create a feature branch
subprocess.run(
["git", "checkout", "-b", "feature/testing"], cwd=repo_path, check=True
)
test_py = repo_path / "test_main.py"
test_py.write_text('''"""Tests for main module."""
import unittest
from main import main
class TestMain(unittest.TestCase):
def test_main(self):
"""Test main function."""
# This is a simple test
self.assertTrue(True)
''')
subprocess.run(["git", "add", "test_main.py"], cwd=repo_path, check=True)
subprocess.run(
["git", "commit", "-m", "Add tests"], cwd=repo_path, check=True
)
# Switch back to main branch
subprocess.run(["git", "checkout", "main"], cwd=repo_path, check=True)
yield repo_path
def test_git_log_parsing(self, tool_helper, test_git_repo):
"""Test parsing git log output."""
# Get git log
result = subprocess.run(
["git", "log", "--pretty=format:%H|%an|%ae|%at|%s", "--name-status"],
cwd=test_git_repo,
capture_output=True,
text=True,
check=True,
)
commits = []
current_commit = None
for line in result.stdout.strip().split("\n"):
if "|" in line and len(line.split("|")) == 5:
# Commit line
hash_, author, email, timestamp, message = line.split("|")
current_commit = {
"hash": hash_,
"author": author,
"email": email,
"timestamp": int(timestamp),
"message": message,
"files": [],
}
commits.append(current_commit)
elif line and current_commit and "\t" in line:
# File change line
parts = line.split("\t")
if len(parts) >= 2:
status, filename = parts[0], parts[1]
current_commit["files"].append(
{"status": status, "filename": filename}
)
assert len(commits) >= 4 # Should have at least 4 commits
assert any(c["message"] == "Initial commit" for c in commits)
assert any(c["message"] == "Add main.py" for c in commits)
assert any("utils" in c["message"] for c in commits)
def test_git_diff_extraction(self, tool_helper, test_git_repo):
"""Test extracting diffs from git history."""
# Get diff for a specific commit
result = subprocess.run(
["git", "log", "-1", "-p", "--format=%H"],
cwd=test_git_repo,
capture_output=True,
text=True,
check=True,
)
assert "diff --git" in result.stdout
assert "@@" in result.stdout # Diff chunks
def test_git_blame_integration(self, tool_helper, test_git_repo):
"""Test git blame for line-level attribution."""
# Run git blame on main.py
result = subprocess.run(
["git", "blame", "--line-porcelain", "main.py"],
cwd=test_git_repo,
capture_output=True,
text=True,
check=True,
)
blame_data = {}
current_commit = None
current_line = None
for line in result.stdout.strip().split("\n"):
if line and not line.startswith("\t"):
parts = line.split(" ")
if len(parts) >= 3 and len(parts[0]) == 40: # SHA-1 hash
current_commit = parts[0]
current_line = int(parts[2])
elif line.startswith("author "):
author = line[7:]
if current_line:
blame_data[current_line] = {
"commit": current_commit,
"author": author,
}
assert len(blame_data) > 0
assert any("Test User" in data["author"] for data in blame_data.values())
def test_full_repo_ingestion(self, tool_helper, test_git_repo):
"""Test ingesting an entire repository into vector store."""
with tempfile.TemporaryDirectory() as vector_dir:
store = InfinityVectorStore(data_path=vector_dir)
# Ingest all Python files
python_files = list(test_git_repo.rglob("*.py"))
assert len(python_files) >= 2
total_docs = 0
for py_file in python_files:
doc_ids = store.add_file(str(py_file), chunk_size=500)
total_docs += len(doc_ids)
assert total_docs > 0
# Search for content
results = store.search("main function", limit=5)
assert len(results) > 0
# Search for imports
import_results = store.search("from utils import", limit=5)
assert len(import_results) > 0
store.close()
def test_git_metadata_extraction(self, tool_helper, test_git_repo):
"""Test extracting and storing git metadata."""
# Get file history
result = subprocess.run(
["git", "log", "--follow", "--pretty=format:%H|%at|%s", "main.py"],
cwd=test_git_repo,
capture_output=True,
text=True,
check=True,
)
history = []
for line in result.stdout.strip().split("\n"):
if line:
parts = line.split("|")
if len(parts) == 3:
history.append(
{
"commit": parts[0],
"timestamp": int(parts[1]),
"message": parts[2],
}
)
assert len(history) >= 2 # main.py was modified at least twice
# Create metadata for vector store
metadata = {
"file_path": "main.py",
"history_count": len(history),
"first_commit": history[-1]["commit"] if history else None,
"last_commit": history[0]["commit"] if history else None,
"last_modified": (
datetime.fromtimestamp(history[0]["timestamp"]).isoformat()
if history
else None
),
}
assert metadata["history_count"] >= 2
assert metadata["first_commit"] != metadata["last_commit"]
class TestGitProjectManager:
"""Test project manager with git repositories."""
@pytest.fixture
def project_manager(self):
"""Create a project manager."""
with tempfile.TemporaryDirectory() as tmpdir:
manager = ProjectVectorManager(
global_db_path=str(Path(tmpdir) / "global_db")
)
yield manager, tmpdir
def test_detect_git_project(self, tool_helper, project_manager):
"""Test detecting git projects."""
manager, base_dir = project_manager
# Create a test git repo
repo_dir = Path(base_dir) / "test_repo"
repo_dir.mkdir()
# Initialize git
subprocess.run(["git", "init"], cwd=repo_dir, check=True)
# Create LLM.md file to mark it as a project
llm_md = repo_dir / "LLM.md"
llm_md.write_text("# Test Project\n\nThis is a test project with git history.")
# Get project info - this will detect the project
project_info = manager.get_project_for_path(str(repo_dir))
assert project_info is not None
assert project_info.name == "test_repo"
assert project_info.llm_md_path.name == "LLM.md"
# Check if .git directory is detected
git_dir = repo_dir / ".git"
assert git_dir.exists()
assert git_dir.is_dir()
class TestGitDiffIngestion:
"""Test ingesting git diffs for code evolution tracking."""
def test_parse_unified_diff(self):
"""Test parsing unified diff format."""
diff_text = """diff --git a/main.py b/main.py
index 1234567..abcdefg 100644
--- a/main.py
+++ b/main.py
@@ -1,5 +1,6 @@
def hello():
- print("Hello")
+ print("Hello, World!")
+ return True
def goodbye():
print("Goodbye")
"""
# Parse diff
changes = []
current_file = None
for line in diff_text.strip().split("\n"):
if line.startswith("diff --git"):
parts = line.split()
if len(parts) >= 4:
current_file = parts[2][2:] # Remove 'a/' prefix
elif line.startswith("@@"):
# Parse chunk header
import re
match = re.match(r"@@ -(\d+),(\d+) \+(\d+),(\d+) @@", line)
if match:
changes.append(
{
"file": current_file,
"old_start": int(match.group(1)),
"old_lines": int(match.group(2)),
"new_start": int(match.group(3)),
"new_lines": int(match.group(4)),
}
)
assert len(changes) == 1
assert changes[0]["file"] == "main.py"
assert changes[0]["old_lines"] == 5
assert changes[0]["new_lines"] == 6
if __name__ == "__main__":
pytest.main([__file__, "-v"])