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()