22474cdba5
Backend (ruff): - Fix 106 errors: move imports to top of file (E402) - Remove unused imports (F401) - Add missing imports for undefined names (F821) - Remove unused variables (F841) - Fix test_models.py broken RefreshToken test - Fix test_projects_api.py missing TestClient import Frontend (eslint): - Remove unused imports/variables across 10 files - Fix explicit any types in client.ts and sessions.ts - Clean up empty block statements in terminal.tsx Quality gates: ruff (pass), eslint (pass), tsc --noEmit (pass), pytest (98 passed, 4 pre-existing failures)
125 lines
4.7 KiB
Python
125 lines
4.7 KiB
Python
"""Unit tests for git mount resolution in tool instances."""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from src.api.tool_instances import (
|
|
_checkout_branch,
|
|
_expand_glob_source,
|
|
_resolve_single_git_mount,
|
|
)
|
|
|
|
|
|
class TestExpandGlobSource:
|
|
"""Unit tests for glob pattern expansion."""
|
|
|
|
def test_no_glob_single_file(self, tmp_path: Path) -> None:
|
|
"""Test non-glob path returns single file."""
|
|
test_file = tmp_path / "test.txt"
|
|
test_file.write_text("content")
|
|
|
|
result = _expand_glob_source(str(test_file), str(tmp_path))
|
|
assert len(result) == 1
|
|
assert result[0] == str(test_file)
|
|
|
|
def test_no_glob_missing_file(self, tmp_path: Path) -> None:
|
|
"""Test non-glob missing file returns empty list."""
|
|
missing_file = tmp_path / "missing.txt"
|
|
|
|
result = _expand_glob_source(str(missing_file), str(tmp_path))
|
|
assert len(result) == 0
|
|
|
|
def test_glob_pattern(self, tmp_path: Path) -> None:
|
|
"""Test glob pattern matches files."""
|
|
(tmp_path / "file1.txt").write_text("content1")
|
|
(tmp_path / "file2.txt").write_text("content2")
|
|
(tmp_path / "other.py").write_text("code")
|
|
|
|
result = _expand_glob_source(str(tmp_path / "*.txt"), str(tmp_path))
|
|
assert len(result) == 2
|
|
assert all(f.endswith(".txt") for f in result)
|
|
|
|
def test_glob_recursive(self, tmp_path: Path) -> None:
|
|
"""Test recursive glob pattern."""
|
|
subdir = tmp_path / "subdir"
|
|
subdir.mkdir()
|
|
(subdir / "nested.txt").write_text("content")
|
|
|
|
result = _expand_glob_source(str(tmp_path / "**" / "*.txt"), str(tmp_path))
|
|
assert len(result) == 1
|
|
assert "nested.txt" in result[0]
|
|
|
|
def test_glob_limit_enforced(self, tmp_path: Path) -> None:
|
|
"""Test that glob matches are limited to prevent abuse."""
|
|
# Create more than 100 files
|
|
for i in range(105):
|
|
(tmp_path / f"file{i}.txt").write_text("content")
|
|
|
|
result = _expand_glob_source(str(tmp_path / "*.txt"), str(tmp_path))
|
|
assert len(result) == 100 # MAX_GLOB_MATCHES limit
|
|
|
|
def test_glob_escapes_repo(self, tmp_path: Path) -> None:
|
|
"""Test that glob results outside repo are filtered."""
|
|
other_dir = tmp_path.parent / "other"
|
|
other_dir.mkdir(exist_ok=True)
|
|
(other_dir / "outside.txt").write_text("content")
|
|
|
|
result = _expand_glob_source(str(tmp_path.parent / "*" / "*.txt"), str(tmp_path))
|
|
# Should only include files within tmp_path, not other_dir
|
|
assert all(r.startswith(str(tmp_path)) for r in result)
|
|
|
|
|
|
class TestCheckoutBranch:
|
|
"""Unit tests for branch checkout."""
|
|
|
|
def test_checkout_existing_branch(self, tmp_path: Path) -> None:
|
|
"""Test checking out an existing branch."""
|
|
# Initialize git repo
|
|
os.system(f"cd {tmp_path} && git init && git config user.email 'test@test.com' && git config user.name 'Test'")
|
|
(tmp_path / "file.txt").write_text("content")
|
|
os.system(f"cd {tmp_path} && git add . && git commit -m 'initial'")
|
|
os.system(f"cd {tmp_path} && git branch feature")
|
|
|
|
_checkout_branch(str(tmp_path), "feature")
|
|
|
|
# Verify we're on feature branch
|
|
result = os.popen(f"cd {tmp_path} && git branch --show-current").read().strip()
|
|
assert result == "feature"
|
|
|
|
def test_checkout_nonexistent_branch(self, tmp_path: Path) -> None:
|
|
"""Test checking out a non-existent branch returns False."""
|
|
os.system(f"cd {tmp_path} && git init && git config user.email 'test@test.com' && git config user.name 'Test'")
|
|
(tmp_path / "file.txt").write_text("content")
|
|
os.system(f"cd {tmp_path} && git add . && git commit -m 'initial'")
|
|
|
|
result = _checkout_branch(str(tmp_path), "nonexistent")
|
|
assert result is False
|
|
|
|
|
|
class TestResolveSingleGitMount:
|
|
"""Unit tests for resolving a single git mount."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_resolve_missing_remote_url(self, db_session) -> None:
|
|
"""Test that missing remote_url returns empty list."""
|
|
git_mount = {
|
|
"source_path": ".",
|
|
"target_path": "/app",
|
|
}
|
|
|
|
result = await _resolve_single_git_mount(db_session, git_mount)
|
|
assert result == []
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_resolve_missing_target_path(self, db_session) -> None:
|
|
"""Test that missing target path returns empty list."""
|
|
git_mount = {
|
|
"remote_url": "https://github.com/user/repo.git",
|
|
"source_path": ".",
|
|
}
|
|
|
|
result = await _resolve_single_git_mount(db_session, git_mount)
|
|
assert result == []
|