"""Tests for hooks/git-commit-confirm.py — file-backed commit messages.""" from conftest import load_hook hook = load_hook("git-commit-confirm.py") # --- existing forms still parse (regression guard) ------------------------- def test_extract_dash_m_simple(): msg = hook.extract_commit_message('git commit -m "fix: tidy parser"') assert msg == "fix: tidy parser" def test_extract_heredoc(): cmd = ( "git commit -m \"$(cat <<'EOF'\n" "feat: add thing\n" "\n" "body line\n" "EOF\n" ")\"" ) msg = hook.extract_commit_message(cmd) assert msg.startswith("feat: add thing") assert "body line" in msg def test_extract_unparseable_falls_through(): # Bare `git commit` would drop into $EDITOR. assert hook.extract_commit_message("git commit") == hook.UNPARSEABLE_MESSAGE # --- new: -F / --file / --file= forms read the file ------------------------ def test_extract_dash_F_reads_file(tmp_path): f = tmp_path / "msg.txt" f.write_text("fix: from a file\n\nsome body\n") assert hook.extract_commit_message(f"git commit -F {f}") == "fix: from a file\n\nsome body" def test_extract_long_file_flag_reads_file(tmp_path): f = tmp_path / "msg.txt" f.write_text("docs: long form file flag\n") assert hook.extract_commit_message(f"git commit --file {f}") == "docs: long form file flag" def test_extract_file_equals_form_reads_file(tmp_path): f = tmp_path / "msg.txt" f.write_text("chore: equals form\n") assert hook.extract_commit_message(f"git commit --file={f}") == "chore: equals form" def test_extract_F_strips_quotes_around_path(tmp_path): f = tmp_path / "my msg.txt" f.write_text("feat: quoted path\n") assert hook.extract_commit_message(f'git commit -F "{f}"') == "feat: quoted path" # --- the audit-item bug: attribution in a file-backed message is now caught - def test_file_backed_attribution_is_caught(tmp_path): f = tmp_path / "msg.txt" f.write_text("feat: add widget\n\nCo-Authored-By: Claude \n") msg = hook.extract_commit_message(f"git commit -F {f}") issues = hook.collect_issues(msg, staged=["a.py"], author="Real Dev ") assert any(i.startswith("AI-attribution") for i in issues) def test_inline_message_without_attribution_is_clean(tmp_path): # Sanity: a clean file-backed message produces no attribution issue. f = tmp_path / "msg.txt" f.write_text("fix: handle empty input\n") msg = hook.extract_commit_message(f"git commit -F {f}") issues = hook.collect_issues(msg, staged=["a.py"], author="Real Dev ") assert not any(i.startswith("AI-attribution") for i in issues) # --- unreadable file falls through to UNPARSEABLE (fail-safe: ask) --------- def test_missing_file_falls_through_to_unparseable(tmp_path): missing = tmp_path / "nope.txt" assert hook.extract_commit_message(f"git commit -F {missing}") == hook.UNPARSEABLE_MESSAGE def test_oversized_file_falls_through_and_hook_asks(tmp_path, monkeypatch): f = tmp_path / "big.txt" f.write_text("x" * 5000) # Force the read to refuse via a tiny limit (simulates oversize). monkeypatch.setattr( hook, "read_referenced_file", lambda p, max_bytes=10: None ) msg = hook.extract_commit_message(f"git commit -F {f}") assert msg == hook.UNPARSEABLE_MESSAGE # And the hook would ask, because UNPARSEABLE_MESSAGE is a flagged issue. issues = hook.collect_issues(msg, staged=["a.py"], author="Dev ") assert any("not parseable" in i for i in issues)