aboutsummaryrefslogtreecommitdiff
path: root/hooks/tests
diff options
context:
space:
mode:
Diffstat (limited to 'hooks/tests')
-rw-r--r--hooks/tests/test_git_commit_confirm.py58
-rw-r--r--hooks/tests/test_rulesets_write_boundary.py110
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