from __future__ import annotations import subprocess from pathlib import Path from tools.capabilities import Capability from tools.runtime import WorktreeTools def init_repo(path: Path) -> WorktreeTools: path.mkdir() subprocess.run(["git", "init", "-b", "main"], cwd=path, check=True, capture_output=True, text=True) return WorktreeTools(path, {Capability.READ_REPOSITORY, Capability.INVESTIGATE_WORKTREE, Capability.WRITE_WORKTREE}) def commit_baseline(path: Path) -> None: subprocess.run(["git", "-c", "user.name=T", "-c", "user.email=t@example.invalid", "add", "."], cwd=path, check=True) subprocess.run(["git", "-c", "user.name=T", "-c", "user.email=t@example.invalid", "commit", "-m", "baseline"], cwd=path, check=True, capture_output=True, text=True) def test_apply_patch_single_hunk_and_git_diff(tmp_path: Path) -> None: tools = init_repo(tmp_path / "repo") (tmp_path / "repo" / "file.txt").write_text("one\ntwo\nthree\n", encoding="utf-8") commit_baseline(tmp_path / "repo") result = tools.apply_patch("file.txt", "@@ -1,3 +1,3 @@\n one\n-two\n+TWO\n three\n") assert result.success assert result.hunks_applied == 1 assert "TWO" in (tmp_path / "repo" / "file.txt").read_text(encoding="utf-8") assert "+TWO" in tools.diff() def test_apply_patch_multiple_hunks(tmp_path: Path) -> None: tools = init_repo(tmp_path / "repo") (tmp_path / "repo" / "file.txt").write_text("a\nb\nc\nd\ne\n", encoding="utf-8") commit_baseline(tmp_path / "repo") result = tools.apply_patch("file.txt", "@@ -1,2 +1,2 @@\n-a\n+A\n b\n@@ -4,2 +4,2 @@\n-d\n+D\n e\n") assert result.success assert result.hunks_applied == 2 assert (tmp_path / "repo" / "file.txt").read_text(encoding="utf-8") == "A\nb\nc\nD\ne\n" def test_apply_patch_relocates_matching_hunk_when_line_number_is_stale(tmp_path: Path) -> None: tools = init_repo(tmp_path / "repo") (tmp_path / "repo" / "file.txt").write_text("header\nalpha\nbeta\ngamma\n", encoding="utf-8") commit_baseline(tmp_path / "repo") result = tools.apply_patch("file.txt", "@@ -1,3 +1,3 @@\n alpha\n-beta\n+BETA\n gamma\n") assert result.success assert (tmp_path / "repo" / "file.txt").read_text(encoding="utf-8") == "header\nalpha\nBETA\ngamma\n" def test_apply_patch_context_mismatch_is_not_partial(tmp_path: Path) -> None: tools = init_repo(tmp_path / "repo") path = tmp_path / "repo" / "file.txt" path.write_text("a\nb\nc\n", encoding="utf-8") commit_baseline(tmp_path / "repo") result = tools.apply_patch("file.txt", "@@ -1,2 +1,2 @@\n-wrong\n+W\n b\n") assert not result.success assert path.read_text(encoding="utf-8") == "a\nb\nc\n" def test_apply_patch_rejects_path_traversal(tmp_path: Path) -> None: tools = init_repo(tmp_path / "repo") result = tools.apply_patch("../outside.txt", "@@ -1 +1 @@\n-a\n+b\n") assert not result.success def test_delete_file_behaviour(tmp_path: Path) -> None: tools = init_repo(tmp_path / "repo") (tmp_path / "repo" / "file.txt").write_text("x\n", encoding="utf-8") (tmp_path / "repo" / "dir").mkdir() commit_baseline(tmp_path / "repo") assert tools.delete_file("file.txt").success assert "D file.txt" in tools.status() assert not tools.delete_file("missing.txt").success assert not tools.delete_file("dir").success assert not tools.delete_file("../outside.txt").success def test_move_file_behaviour(tmp_path: Path) -> None: tools = init_repo(tmp_path / "repo") repo = tmp_path / "repo" (repo / "source.txt").write_text("content", encoding="utf-8") (repo / "existing.txt").write_text("existing", encoding="utf-8") commit_baseline(repo) assert tools.move_file("source.txt", "nested/dest.txt").success assert (repo / "nested" / "dest.txt").read_text(encoding="utf-8") == "content" assert not tools.move_file("missing.txt", "x.txt").success assert not tools.move_file("nested/dest.txt", "existing.txt").success assert not tools.move_file("nested/dest.txt", "../outside.txt").success assert "source.txt" in tools.status() assert "nested/" in tools.status() def test_create_directory_behaviour(tmp_path: Path) -> None: tools = init_repo(tmp_path / "repo") repo = tmp_path / "repo" (repo / "file.txt").write_text("x", encoding="utf-8") assert tools.create_directory("a").success assert tools.create_directory("a/b/c").success assert tools.create_directory("a/b/c").success assert not tools.create_directory("file.txt").success assert not tools.create_directory("../outside").success