115 lines
4.5 KiB
Python
115 lines
4.5 KiB
Python
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
|