From 9d5641fe6585a61894f7b7427a68c794e5efe76d Mon Sep 17 00:00:00 2001 From: godosa Date: Wed, 7 Oct 2026 07:44:16 +0200 Subject: Denylist guard at commit time (pre-commit + commit-msg hooks); drop publish_snapshot Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01JEAjUkQRrCYX5MZhWxdtj2 --- tests/test_denylist_check.py | 145 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 tests/test_denylist_check.py (limited to 'tests/test_denylist_check.py') diff --git a/tests/test_denylist_check.py b/tests/test_denylist_check.py new file mode 100644 index 0000000..3a7fa36 --- /dev/null +++ b/tests/test_denylist_check.py @@ -0,0 +1,145 @@ +import os +import shutil +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(ROOT / "scripts")) +import denylist_check as D # noqa: E402 + +ENV = {**os.environ, "GIT_AUTHOR_NAME": "t", "GIT_AUTHOR_EMAIL": "t@t", "GIT_COMMITTER_NAME": "t", + "GIT_COMMITTER_EMAIL": "t@t", "GIT_CONFIG_GLOBAL": "/dev/null", "GIT_CONFIG_NOSYSTEM": "1"} + + +def git(cwd, *args, check=True): + return subprocess.run(["git", *args], cwd=cwd, capture_output=True, text=True, env=ENV, check=check) + + +class ParseTest(unittest.TestCase): + def test_words_comments_exemptions(self): + words, exempt = D.parse("# c\nSecretProj\n\n Bob \n!bobcat\n") + self.assertEqual(words, [(2, "secretproj"), (4, "bob")]) + self.assertEqual(exempt, ["bobcat"]) + + def test_hits_case_insensitive_and_exempt(self): + words, exempt = [(1, "bob"), (2, "secretproj")], ["bobcat"] + self.assertEqual(D.hits("a BOB and a Bobcat", words, exempt), [1]) + self.assertEqual(D.hits("only a bobcat here", words, exempt), []) + self.assertEqual(D.hits("x SecretProj y", words, exempt), [2]) + self.assertEqual(D.hits("clean", words, exempt), []) + + +class HookTest(unittest.TestCase): + """Real temp repo with the committed hook dir; commits through git itself.""" + + def setUp(self): + self.tmp = tempfile.TemporaryDirectory() + self.repo = Path(self.tmp.name) / "r" + self.repo.mkdir() + git(self.repo, "init", "-q", "-b", "master") + shutil.copytree(ROOT / ".githooks", self.repo / ".githooks") + (self.repo / "scripts").mkdir() + shutil.copy(ROOT / "scripts" / "denylist_check.py", self.repo / "scripts") + (self.repo / ".gitignore").write_text("publish-denylist.local\n") + git(self.repo, "config", "core.hooksPath", ".githooks") + (self.repo / "publish-denylist.local").write_text("# private\nSecretProj\n!secretprojector\n") + self.put("a.txt", "hello\n") + git(self.repo, "add", ".githooks", "scripts", ".gitignore") + r = self.commit("init") + self.assertEqual(r.returncode, 0, r.stderr) + + def tearDown(self): + self.tmp.cleanup() + + def put(self, path, text): + f = self.repo / path + f.parent.mkdir(parents=True, exist_ok=True) + f.write_text(text) + git(self.repo, "add", path) + + def commit(self, msg, cwd=None): + return git(cwd or self.repo, "commit", "-q", "-m", msg, check=False) + + def head(self, cwd=None): + return git(cwd or self.repo, "log", "--format=%s").stdout.splitlines() + + def test_content_hit_blocks_without_printing_word(self): + self.put("b.txt", "x\nsee secretproj docs\n") + r = self.commit("add b") + self.assertEqual(r.returncode, 1) + self.assertIn("b.txt:2: word #2", r.stderr) + self.assertNotIn("secretproj", r.stderr.lower()) + self.assertEqual(self.head(), ["init"]) + + def test_path_hit_blocks(self): + self.put("docs/SECRETPROJ-notes.md", "clean\n") + r = self.commit("add notes") + self.assertEqual(r.returncode, 1) + self.assertIn("path docs/***-notes.md: word #2", r.stderr) + self.assertNotIn("secretproj", r.stderr.lower()) + + def test_message_hit_blocks(self): + self.put("b.txt", "clean\n") + r = self.commit("port from SecretProj") + self.assertEqual(r.returncode, 1) + self.assertIn("commit message:1: word #2", r.stderr) + self.assertEqual(self.head(), ["init"]) + + def test_clean_and_exempt_pass(self): + self.put("b.txt", "the secretprojector is fine\n") + r = self.commit("clean msg") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertEqual(self.head(), ["clean msg", "init"]) + + def test_only_added_lines_count(self): + (self.repo / "publish-denylist.local").write_text("") # let an old hit in + self.put("old.txt", "SecretProj legacy\n") + self.assertEqual(self.commit("old").returncode, 0) + (self.repo / "publish-denylist.local").write_text("SecretProj\n") + self.put("old.txt", "SecretProj legacy\nnew clean line\n") + r = self.commit("touch old") + self.assertEqual(r.returncode, 0, r.stderr) + + def test_missing_denylist_warns_not_fails(self): + (self.repo / "publish-denylist.local").unlink() + self.put("b.txt", "SecretProj\n") + r = self.commit("no list") + self.assertEqual(r.returncode, 0, r.stderr) + self.assertIn("warning: no publish-denylist.local", r.stderr) + + def test_linked_worktree_uses_main_tree_list(self): + wt = Path(self.tmp.name) / "wt" + git(self.repo, "worktree", "add", "-q", str(wt), "-b", "topic") + self.assertFalse((wt / "publish-denylist.local").exists()) + (wt / "c.txt").write_text("SecretProj\n") + git(wt, "add", "c.txt") + r = self.commit("wt", cwd=wt) + self.assertEqual(r.returncode, 1) + self.assertIn("c.txt:1: word #2", r.stderr) + + def test_extra_list_from_git_config_merged(self): + glob = Path(self.tmp.name) / "global.txt" + glob.write_text("Alpha\n") + git(self.repo, "config", "--add", "denylist.file", str(glob)) + self.put("b.txt", "alpha\n") + r = self.commit("b") + self.assertEqual(r.returncode, 1) + self.assertIn("b.txt:1: global.txt word #1", r.stderr) + self.assertNotIn("alpha", r.stderr.lower()) + + def test_tree_mode_scans_tracked_files(self): + (self.repo / "publish-denylist.local").write_text("") + self.put("old.txt", "x SecretProj\n") + self.assertEqual(self.commit("old").returncode, 0) + (self.repo / "publish-denylist.local").write_text("SecretProj\n") + r = subprocess.run([sys.executable, "scripts/denylist_check.py", "tree"], cwd=self.repo, + capture_output=True, text=True, env=ENV) + self.assertEqual(r.returncode, 1) + self.assertIn("old.txt:1: word #1", r.stderr) + + +if __name__ == "__main__": + unittest.main() -- cgit