aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/tests
diff options
context:
space:
mode:
authorgodosa <godosa@godosa.eu>2026-10-07 07:44:16 +0200
committergodosa <godosa@godosa.eu>2026-10-07 07:44:16 +0200
commit9d5641fe6585a61894f7b7427a68c794e5efe76d (patch)
tree79ebae3131df1c50542420db9e5ecb44e3d22076 /tests
parent81d4e80fd5aabe4e80f58e960affa795cf7d34ec (diff)
downloadworkflow-9d5641fe6585a61894f7b7427a68c794e5efe76d.tar.gz
workflow-9d5641fe6585a61894f7b7427a68c794e5efe76d.zip
Denylist guard at commit time (pre-commit + commit-msg hooks); drop publish_snapshot
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JEAjUkQRrCYX5MZhWxdtj2
Diffstat (limited to 'tests')
-rw-r--r--tests/test_denylist_check.py145
-rw-r--r--tests/test_publish_snapshot.py122
2 files changed, 145 insertions, 122 deletions
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()
diff --git a/tests/test_publish_snapshot.py b/tests/test_publish_snapshot.py
deleted file mode 100644
index 3243d4b..0000000
--- a/tests/test_publish_snapshot.py
+++ /dev/null
@@ -1,122 +0,0 @@
-import os
-import subprocess
-import sys
-import tempfile
-import unittest
-from pathlib import Path
-
-SCRIPT = Path(__file__).resolve().parent.parent / "scripts" / "publish_snapshot.py"
-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"}
-
-
-def git(cwd, *args):
- return subprocess.run(["git", *args], cwd=cwd, capture_output=True, text=True, env=ENV, check=True).stdout
-
-
-class PublishSnapshotTest(unittest.TestCase):
- def setUp(self):
- self.tmp = tempfile.TemporaryDirectory()
- t = Path(self.tmp.name)
- self.src, self.dest, self.deny = t / "tool", t / "pub", t / "deny.txt"
- self.src.mkdir()
- git(self.src, "init", "-q", "-b", "master")
- self.put({"wf.py": "print('hi')\n", "CHANGES.md": "# Changes\n\n- 2026-01-01 first.\n",
- "wflib/a.py": "x = 1\n", "docs/d.md": "doc\n",
- "inbox.md": "tracked inbox\n", "out/log": "x\n", "sub/__pycache__/c.pyc": "bin\n"})
- (self.src / "wf.py").chmod(0o755)
- self.commit("one")
- self.deny.write_text("# private\nSecretProj\n\n")
-
- def tearDown(self):
- self.tmp.cleanup()
-
- def put(self, files):
- for p, text in files.items():
- f = self.src / p
- f.parent.mkdir(parents=True, exist_ok=True)
- f.write_text(text)
-
- def commit(self, msg):
- git(self.src, "add", "-A")
- git(self.src, "commit", "-qm", msg)
-
- def run_it(self, *extra):
- return subprocess.run([sys.executable, str(SCRIPT), str(self.dest), "--src", str(self.src),
- "--denylist", str(self.deny), *extra], capture_output=True, text=True, env=ENV)
-
- def files(self):
- return sorted(git(self.dest, "ls-files").split())
-
- def test_first_run_one_commit_excludes(self):
- r = self.run_it()
- self.assertEqual(r.returncode, 0, r.stderr)
- self.assertEqual(self.files(), ["CHANGES.md", "docs/d.md", "wf.py", "wflib/a.py"])
- self.assertEqual(git(self.dest, "log", "--format=%s").splitlines(), ["initial public snapshot"])
- self.assertTrue(os.access(self.dest / "wf.py", os.X_OK))
- self.assertEqual(git(self.dest, "remote"), "")
-
- def test_denylist_hit_content_and_path_writes_nothing(self):
- self.put({"docs/d.md": "doc\nsee secretproj here\n", "SecretProj.md": "x\n"})
- self.commit("leak")
- r = self.run_it()
- self.assertEqual(r.returncode, 1)
- self.assertIn("docs/d.md:2: SecretProj", r.stderr)
- self.assertIn("SecretProj.md: SecretProj (path)", r.stderr)
- self.assertFalse(self.dest.exists())
-
- def test_uncommitted_files_not_published(self):
- (self.src / "wip.py").write_text("SecretProj\n")
- r = self.run_it()
- self.assertEqual(r.returncode, 0, r.stderr)
- self.assertNotIn("wip.py", self.files())
-
- def test_missing_or_empty_denylist_refuses(self):
- self.deny.unlink()
- r = self.run_it()
- self.assertEqual(r.returncode, 1)
- self.assertIn("no denylist", r.stderr)
- self.deny.write_text("# only comments\n")
- self.assertEqual(self.run_it().returncode, 1)
- self.assertFalse(self.dest.exists())
-
- def test_second_run_commit_message_new_changes_lines(self):
- self.run_it()
- self.put({"CHANGES.md": "# Changes\n\n- 2026-01-03 third.\n- 2026-01-02 second.\n- 2026-01-01 first.\n",
- "wflib/b.py": "y = 2\n"})
- (self.src / "docs/d.md").unlink()
- self.commit("two")
- r = self.run_it()
- self.assertEqual(r.returncode, 0, r.stderr)
- self.assertEqual(git(self.dest, "log", "-1", "--format=%B").strip(),
- "public snapshot\n\n- 2026-01-03 third.\n- 2026-01-02 second.")
- self.assertEqual(self.files(), ["CHANGES.md", "wf.py", "wflib/a.py", "wflib/b.py"])
- self.assertEqual(len(git(self.dest, "log", "--format=%h").split()), 2)
-
- def test_no_change_no_commit(self):
- self.run_it()
- r = self.run_it()
- self.assertEqual(r.returncode, 0, r.stderr)
- self.assertIn("nothing new", r.stdout)
- self.assertEqual(len(git(self.dest, "log", "--format=%h").split()), 1)
-
- def test_ref_option_and_never_touches_remote(self):
- self.run_it()
- git(self.dest, "remote", "add", "home", "/nonexistent")
- self.put({"wflib/a.py": "x = 2\n"})
- self.commit("two")
- self.assertEqual(self.run_it("--ref", "HEAD~1").stdout.strip(), "nothing new: no commit")
- r = self.run_it()
- self.assertIn("committed", r.stdout)
- self.assertEqual(git(self.dest, "remote").split(), ["home"])
-
- def test_nonempty_non_git_dest_refused(self):
- self.dest.mkdir()
- (self.dest / "keep.txt").write_text("x")
- r = self.run_it()
- self.assertEqual(r.returncode, 1)
- self.assertIn("not a git repo", r.stderr)
-
-
-if __name__ == "__main__":
- unittest.main()