|
| 1 | +"""Safety tests for scripts/build_plugin_dist.py --out. No Blender required. |
| 2 | +
|
| 3 | +build() replaces --out wholesale, so the script must refuse a target it |
| 4 | +would destroy (the repo, a parent of it, a git checkout, or a non-empty |
| 5 | +directory without --force) before touching anything. Run: |
| 6 | + python tests/test_build_plugin_dist.py -v |
| 7 | +""" |
| 8 | +from __future__ import annotations |
| 9 | + |
| 10 | +import contextlib |
| 11 | +import io |
| 12 | +import sys |
| 13 | +import tempfile |
| 14 | +import unittest |
| 15 | +from pathlib import Path |
| 16 | + |
| 17 | +REPO = Path(__file__).resolve().parent.parent |
| 18 | +sys.path.insert(0, str(REPO / "scripts")) |
| 19 | + |
| 20 | +import build_plugin_dist as b # noqa: E402 |
| 21 | + |
| 22 | + |
| 23 | +def run(argv): |
| 24 | + err = io.StringIO() |
| 25 | + with contextlib.redirect_stderr(err), contextlib.redirect_stdout(io.StringIO()): |
| 26 | + code = b.main(argv) |
| 27 | + return code, err.getvalue() |
| 28 | + |
| 29 | + |
| 30 | +class OutGuard(unittest.TestCase): |
| 31 | + # The repo and its parents are checked through unsafe_out() directly, never |
| 32 | + # through main(): if the guard regressed, main() would delete the checkout. |
| 33 | + def test_refuses_repo_root(self): |
| 34 | + self.assertIn("repository", b.unsafe_out(REPO, force=True) or "") |
| 35 | + |
| 36 | + def test_refuses_parent_of_repo(self): |
| 37 | + self.assertIn("repository", b.unsafe_out(REPO.parent, force=True) or "") |
| 38 | + |
| 39 | + def test_refuses_non_empty_dir_without_force(self): |
| 40 | + with tempfile.TemporaryDirectory() as td: |
| 41 | + keep = Path(td) / "important.txt" |
| 42 | + keep.write_text("keep", encoding="utf-8") |
| 43 | + code, err = run(["--out", td]) |
| 44 | + self.assertEqual(code, 2) |
| 45 | + self.assertIn("--force", err) |
| 46 | + self.assertTrue(keep.is_file()) |
| 47 | + |
| 48 | + def test_refuses_git_checkout_even_with_force(self): |
| 49 | + with tempfile.TemporaryDirectory() as td: |
| 50 | + (Path(td) / ".git").mkdir() |
| 51 | + code, err = run(["--out", td, "--force"]) |
| 52 | + self.assertEqual(code, 2) |
| 53 | + self.assertIn(".git", err) |
| 54 | + self.assertTrue((Path(td) / ".git").is_dir()) |
| 55 | + |
| 56 | + def test_builds_into_missing_dir(self): |
| 57 | + with tempfile.TemporaryDirectory() as td: |
| 58 | + out = Path(td) / "dist" |
| 59 | + code, _ = run(["--out", str(out)]) |
| 60 | + self.assertEqual(code, 0) |
| 61 | + self.assertTrue((out / ".claude-plugin" / "plugin.json").is_file()) |
| 62 | + |
| 63 | + def test_force_replaces_previous_build(self): |
| 64 | + with tempfile.TemporaryDirectory() as td: |
| 65 | + out = Path(td) / "dist" |
| 66 | + self.assertEqual(run(["--out", str(out)])[0], 0) |
| 67 | + (out / "stale.txt").write_text("old", encoding="utf-8") |
| 68 | + self.assertEqual(run(["--out", str(out)])[0], 2) |
| 69 | + self.assertEqual(run(["--out", str(out), "--force"])[0], 0) |
| 70 | + self.assertFalse((out / "stale.txt").exists()) |
| 71 | + |
| 72 | + |
| 73 | +if __name__ == "__main__": |
| 74 | + unittest.main() |
0 commit comments