diff options
Diffstat (limited to 'hooks/tests')
| -rw-r--r-- | hooks/tests/test_git_commit_confirm.py | 58 | ||||
| -rw-r--r-- | hooks/tests/test_rulesets_write_boundary.py | 110 |
2 files changed, 168 insertions, 0 deletions
diff --git a/hooks/tests/test_git_commit_confirm.py b/hooks/tests/test_git_commit_confirm.py index 83519ad..4bf95cf 100644 --- a/hooks/tests/test_git_commit_confirm.py +++ b/hooks/tests/test_git_commit_confirm.py @@ -95,3 +95,61 @@ def test_oversized_file_falls_through_and_hook_asks(tmp_path, monkeypatch): # And the hook would ask, because UNPARSEABLE_MESSAGE is a flagged issue. issues = hook.collect_issues(msg, staged=["a.py"], author="Dev <d@e.com>") assert any("not parseable" in i for i in issues) + + +# --- bundled test-run + commit: the hard gate ------------------------------ + +def test_bundle_semicolon_make_test_is_flagged(): + assert hook.detect_bundled_test_run('make test; git commit -m "x"') + + +def test_bundle_ampersand_gated_is_allowed(): + # `&&` runs the commit only on a green suite — safe, not flagged. + assert hook.detect_bundled_test_run('make test && git commit -m "x"') is None + + +def test_bundle_pytest_semicolon_is_flagged(): + assert hook.detect_bundled_test_run('pytest ; git commit -m "x"') + + +def test_bundle_npm_test_is_flagged(): + assert hook.detect_bundled_test_run('npm test; git commit -m "x"') + + +def test_bundle_go_test_is_flagged(): + assert hook.detect_bundled_test_run('go test ./...; git commit -m "x"') + + +def test_bundle_cargo_test_is_flagged(): + assert hook.detect_bundled_test_run('cargo test ; git commit -m "x"') + + +def test_bundle_bats_is_flagged(): + assert hook.detect_bundled_test_run('bats tests/ ; git commit -m "x"') + + +def test_bundle_pipe_masks_exit_is_flagged(): + # `make test | tee log` exits with tee's status, so && gates on tee, not + # the suite — a red suite would still commit. Flag it. + assert hook.detect_bundled_test_run('make test | tee log && git commit -m "x"') + + +def test_bundle_or_connector_is_flagged(): + assert hook.detect_bundled_test_run('make test || git commit -m "x"') + + +def test_runner_only_in_message_is_not_flagged(): + # "make test" inside the commit message must not trip the detector. + assert hook.detect_bundled_test_run('git commit -m "remember to make test"') is None + + +def test_plain_commit_is_not_flagged(): + assert hook.detect_bundled_test_run('git commit -m "fix: thing"') is None + + +def test_gated_chain_before_commit_is_allowed(): + assert hook.detect_bundled_test_run('cd proj && pytest && git commit -m "x"') is None + + +def test_empty_command_is_not_flagged(): + assert hook.detect_bundled_test_run("") is None diff --git a/hooks/tests/test_rulesets_write_boundary.py b/hooks/tests/test_rulesets_write_boundary.py new file mode 100644 index 0000000..826a941 --- /dev/null +++ b/hooks/tests/test_rulesets_write_boundary.py @@ -0,0 +1,110 @@ +import json +import os +import subprocess +import sys +from pathlib import Path + + +SCRIPT = Path(__file__).parents[1] / "rulesets-write-boundary.py" + + +def run_hook(payload: dict, rulesets: Path) -> dict | None: + proc = subprocess.run( + [sys.executable, str(SCRIPT)], + input=json.dumps(payload), + text=True, + capture_output=True, + env={**os.environ, "RULESETS_ROOT": str(rulesets)}, + check=True, + ) + return json.loads(proc.stdout) if proc.stdout else None + + +def test_allows_write_from_rulesets_session(tmp_path): + rulesets = tmp_path / "rulesets" + rulesets.mkdir() + target = rulesets / "file" + result = run_hook( + { + "cwd": str(rulesets), + "tool_name": "Write", + "tool_input": {"file_path": str(target)}, + }, + rulesets, + ) + assert result is None + + +def test_blocks_absolute_cross_project_write(tmp_path): + rulesets = tmp_path / "rulesets" + other = tmp_path / "other" + rulesets.mkdir() + other.mkdir() + result = run_hook( + { + "cwd": str(other), + "tool_name": "Edit", + "tool_input": {"file_path": str(rulesets / "rule.md")}, + }, + rulesets, + ) + assert result["hookSpecificOutput"]["permissionDecision"] == "deny" + assert "inbox-send rulesets" in result["hookSpecificOutput"][ + "permissionDecisionReason" + ] + + +def test_blocks_write_reached_through_symlink(tmp_path): + rulesets = tmp_path / "rulesets" + other = tmp_path / "other" + installed = tmp_path / "installed" + rulesets.mkdir() + other.mkdir() + (rulesets / "rules").mkdir() + installed.symlink_to(rulesets / "rules", target_is_directory=True) + result = run_hook( + { + "cwd": str(other), + "tool_name": "Write", + "tool_input": {"file_path": str(installed / "todo-format.md")}, + }, + rulesets, + ) + assert result["hookSpecificOutput"]["permissionDecision"] == "deny" + assert str(rulesets) in result["systemMessage"] + + +def test_blocks_apply_patch_target(tmp_path): + rulesets = tmp_path / "rulesets" + other = tmp_path / "other" + rulesets.mkdir() + other.mkdir() + patch = ( + f"*** Begin Patch\n*** Update File: {rulesets / 'file'}\n" + "@@\n-old\n+new\n*** End Patch\n" + ) + result = run_hook( + { + "cwd": str(other), + "tool_name": "apply_patch", + "tool_input": {"input": patch}, + }, + rulesets, + ) + assert result["hookSpecificOutput"]["permissionDecision"] == "deny" + + +def test_allows_unrelated_write(tmp_path): + rulesets = tmp_path / "rulesets" + other = tmp_path / "other" + rulesets.mkdir() + other.mkdir() + result = run_hook( + { + "cwd": str(other), + "tool_name": "Edit", + "tool_input": {"file_path": str(other / "file")}, + }, + rulesets, + ) + assert result is None |
