Spaces:
Running
Running
| # tests/test_github_tool.py | |
| import concurrent.futures | |
| from pathlib import Path | |
| from unittest.mock import MagicMock, patch | |
| import pytest | |
| from app.tools.github_tool import ( | |
| _get_dir_size_mb, | |
| clone_repository, | |
| delete_repository, | |
| get_changed_files, | |
| resolve_remote_head_sha, | |
| ) | |
| # ββ _get_dir_size_mb ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_get_dir_size_mb_sums_file_sizes(tmp_path: Path) -> None: | |
| (tmp_path / "a.txt").write_bytes(b"x" * 1024) | |
| (tmp_path / "b.txt").write_bytes(b"x" * 1024) | |
| size = _get_dir_size_mb(tmp_path) | |
| assert size == pytest.approx(2 / 1024, rel=0.01) | |
| # ββ get_changed_files βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_get_changed_files_returns_py_files(tmp_path: Path) -> None: | |
| (tmp_path / "changed.py").write_text("x = 1") | |
| mock_result = MagicMock() | |
| mock_result.returncode = 0 | |
| mock_result.stdout = "changed.py\nREADME.md\n" | |
| with patch("app.tools.github_tool.subprocess.run", return_value=mock_result): | |
| files = get_changed_files(str(tmp_path), "abc123", "def456") | |
| assert files == [str(tmp_path / "changed.py") if False else "changed.py"] | |
| # only .py files that exist on disk are returned | |
| assert all(f.endswith(".py") for f in files) | |
| def test_get_changed_files_returns_empty_on_git_error() -> None: | |
| mock_result = MagicMock() | |
| mock_result.returncode = 1 | |
| with patch("app.tools.github_tool.subprocess.run", return_value=mock_result): | |
| files = get_changed_files("/fake/path", "abc", "def") | |
| assert files == [] | |
| def test_get_changed_files_filters_nonexistent_files(tmp_path: Path) -> None: | |
| mock_result = MagicMock() | |
| mock_result.returncode = 0 | |
| mock_result.stdout = "ghost.py\n" # file does NOT exist on disk | |
| with patch("app.tools.github_tool.subprocess.run", return_value=mock_result): | |
| files = get_changed_files(str(tmp_path), "abc", "def") | |
| assert files == [] | |
| # ββ clone_repository ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_clone_repository_rejects_invalid_url() -> None: | |
| with pytest.raises(ValueError, match="Invalid GitHub URL"): | |
| clone_repository("https://gitlab.com/owner/repo") | |
| def test_clone_repository_strips_dot_git_suffix(tmp_path: Path) -> None: | |
| with ( | |
| patch("app.tools.github_tool.settings") as mock_settings, | |
| patch("app.tools.github_tool.Repo.clone_from"), | |
| patch("app.tools.github_tool._get_dir_size_mb", return_value=1.0), | |
| ): | |
| mock_settings.repo_clone_dir = str(tmp_path) | |
| mock_settings.max_repo_size_mb = 300 | |
| result = clone_repository("https://github.com/owner/myrepo.git") | |
| assert "myrepo" in result | |
| assert ".git" not in Path(result).name | |
| def test_clone_repository_success(tmp_path: Path) -> None: | |
| with ( | |
| patch("app.tools.github_tool.settings") as mock_settings, | |
| patch("app.tools.github_tool.Repo.clone_from"), | |
| patch("app.tools.github_tool._get_dir_size_mb", return_value=10.0), | |
| ): | |
| mock_settings.repo_clone_dir = str(tmp_path) | |
| mock_settings.max_repo_size_mb = 300 | |
| result = clone_repository("https://github.com/owner/repo") | |
| assert "repo" in result | |
| def test_clone_repository_raises_on_timeout(tmp_path: Path) -> None: | |
| def slow_clone(*args: object, **kwargs: object) -> None: | |
| raise concurrent.futures.TimeoutError() | |
| with ( | |
| patch("app.tools.github_tool.settings") as mock_settings, | |
| patch("app.tools.github_tool.Repo.clone_from", side_effect=slow_clone), | |
| patch("app.tools.github_tool._get_dir_size_mb", return_value=1.0), | |
| ): | |
| mock_settings.repo_clone_dir = str(tmp_path) | |
| mock_settings.max_repo_size_mb = 300 | |
| with pytest.raises(TimeoutError, match="timed out"): | |
| clone_repository("https://github.com/owner/repo") | |
| def test_clone_repository_raises_on_clone_error(tmp_path: Path) -> None: | |
| with ( | |
| patch("app.tools.github_tool.settings") as mock_settings, | |
| patch( | |
| "app.tools.github_tool.Repo.clone_from", | |
| side_effect=RuntimeError("auth failed"), | |
| ), | |
| ): | |
| mock_settings.repo_clone_dir = str(tmp_path) | |
| mock_settings.max_repo_size_mb = 300 | |
| with pytest.raises(RuntimeError, match="auth failed"): | |
| clone_repository("https://github.com/owner/repo") | |
| def test_clone_repository_raises_when_too_large(tmp_path: Path) -> None: | |
| with ( | |
| patch("app.tools.github_tool.settings") as mock_settings, | |
| patch("app.tools.github_tool.Repo.clone_from"), | |
| patch("app.tools.github_tool._get_dir_size_mb", return_value=999.0), | |
| ): | |
| mock_settings.repo_clone_dir = str(tmp_path) | |
| mock_settings.max_repo_size_mb = 300 | |
| with pytest.raises(ValueError, match="exceeds limit"): | |
| clone_repository("https://github.com/owner/repo") | |
| # ββ delete_repository βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_delete_repository_removes_existing_path(tmp_path: Path) -> None: | |
| target = tmp_path / "cloned_repo" | |
| target.mkdir() | |
| (target / "file.py").write_text("x = 1") | |
| delete_repository(str(target)) | |
| assert not target.exists() | |
| def test_delete_repository_is_noop_for_missing_path(tmp_path: Path) -> None: | |
| missing = str(tmp_path / "does_not_exist") | |
| delete_repository(missing) # must not raise | |
| # ββ resolve_remote_head_sha βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_resolve_remote_head_sha_returns_sha_on_success() -> None: | |
| mock_result = MagicMock() | |
| mock_result.returncode = 0 | |
| mock_result.stdout = "abc123def456\tHEAD\n" | |
| with patch("app.tools.github_tool.subprocess.run", return_value=mock_result): | |
| sha = resolve_remote_head_sha("https://github.com/owner/repo") | |
| assert sha == "abc123def456" | |
| def test_resolve_remote_head_sha_returns_none_on_git_error() -> None: | |
| mock_result = MagicMock() | |
| mock_result.returncode = 1 | |
| mock_result.stdout = "" | |
| with patch("app.tools.github_tool.subprocess.run", return_value=mock_result): | |
| sha = resolve_remote_head_sha("https://github.com/owner/repo") | |
| assert sha is None | |
| def test_resolve_remote_head_sha_returns_none_on_exception() -> None: | |
| with patch("app.tools.github_tool.subprocess.run", side_effect=OSError("boom")): | |
| sha = resolve_remote_head_sha("https://github.com/owner/repo") | |
| assert sha is None |