test_cmd_hooks.py
python
sha256:4802c0281407b5de70ce1b34bbe1d1cd055533cbd4b8be35861a6a3eb545c1dc
feat(#192): Phase 1 — .musehooks.toml parser + `muse hooks list`
Sonnet 5
patch
3 days ago
| 1 | """Tests for ``muse hooks`` CLI — musehub#192 Phase 1: HK_03 (`muse hooks list`). |
| 2 | |
| 3 | Phase 1 scope only: reads the tracked ``.musehooks.toml`` and prints its |
| 4 | contents. No local activation state yet (Phase 2) and no ``muse commit`` |
| 5 | integration yet (Phase 3). |
| 6 | """ |
| 7 | |
| 8 | from __future__ import annotations |
| 9 | |
| 10 | import json |
| 11 | import pathlib |
| 12 | |
| 13 | from tests.cli_test_helper import CliRunner |
| 14 | |
| 15 | runner = CliRunner() |
| 16 | |
| 17 | |
| 18 | def _invoke(path: pathlib.Path, args: list[str]): |
| 19 | import os |
| 20 | saved = os.getcwd() |
| 21 | try: |
| 22 | os.chdir(path) |
| 23 | return runner.invoke(None, args) |
| 24 | finally: |
| 25 | os.chdir(saved) |
| 26 | |
| 27 | |
| 28 | def _init_repo(path: pathlib.Path, domain: str = "code") -> None: |
| 29 | r = _invoke(path, ["init", "--domain", domain]) |
| 30 | assert r.exit_code == 0, r.output |
| 31 | |
| 32 | |
| 33 | def _write_hooks(path: pathlib.Path, content: str) -> None: |
| 34 | (path / ".musehooks.toml").write_text(content, encoding="utf-8") |
| 35 | |
| 36 | |
| 37 | class TestHooksListNoFile: |
| 38 | def test_no_musehooks_file_json_reports_empty(self, tmp_path: pathlib.Path) -> None: |
| 39 | _init_repo(tmp_path) |
| 40 | r = _invoke(tmp_path, ["hooks", "list", "--json"]) |
| 41 | assert r.exit_code == 0, r.output |
| 42 | data = json.loads(r.output) |
| 43 | assert data["hooks"] == {} |
| 44 | |
| 45 | def test_no_musehooks_file_text_says_none_defined(self, tmp_path: pathlib.Path) -> None: |
| 46 | _init_repo(tmp_path) |
| 47 | r = _invoke(tmp_path, ["hooks", "list"]) |
| 48 | assert r.exit_code == 0, r.output |
| 49 | assert "no" in r.output.lower() or "none" in r.output.lower() |
| 50 | |
| 51 | |
| 52 | class TestHooksListWithFile: |
| 53 | def test_json_reports_pre_commit_commands(self, tmp_path: pathlib.Path) -> None: |
| 54 | _init_repo(tmp_path) |
| 55 | _write_hooks( |
| 56 | tmp_path, |
| 57 | '[pre-commit]\ncommands = ["muse agent-config status --json"]\n', |
| 58 | ) |
| 59 | r = _invoke(tmp_path, ["hooks", "list", "--json"]) |
| 60 | assert r.exit_code == 0, r.output |
| 61 | data = json.loads(r.output) |
| 62 | assert data["hooks"]["pre-commit"] == ["muse agent-config status --json"] |
| 63 | |
| 64 | def test_text_mode_shows_hook_point_and_commands(self, tmp_path: pathlib.Path) -> None: |
| 65 | _init_repo(tmp_path) |
| 66 | _write_hooks( |
| 67 | tmp_path, |
| 68 | '[pre-commit]\ncommands = ["muse agent-config status --json"]\n', |
| 69 | ) |
| 70 | r = _invoke(tmp_path, ["hooks", "list"]) |
| 71 | assert r.exit_code == 0, r.output |
| 72 | assert "pre-commit" in r.output |
| 73 | assert "muse agent-config status --json" in r.output |
| 74 | |
| 75 | |
| 76 | class TestHooksListErrors: |
| 77 | def test_malformed_toml_exits_1_with_clear_message(self, tmp_path: pathlib.Path) -> None: |
| 78 | _init_repo(tmp_path) |
| 79 | _write_hooks(tmp_path, "[pre-commit\ncommands = [") |
| 80 | r = _invoke(tmp_path, ["hooks", "list", "--json"]) |
| 81 | assert r.exit_code == 1 |
| 82 | assert "parse error" in r.stderr.lower() |
| 83 | |
| 84 | def test_unknown_hook_point_exits_1_with_clear_message(self, tmp_path: pathlib.Path) -> None: |
| 85 | _init_repo(tmp_path) |
| 86 | _write_hooks(tmp_path, '[bogus-point]\ncommands = ["echo hi"]\n') |
| 87 | r = _invoke(tmp_path, ["hooks", "list", "--json"]) |
| 88 | assert r.exit_code == 1 |
| 89 | assert "unknown hook point" in r.stderr.lower() |
| 90 | |
| 91 | def test_not_a_repo_exits_2(self, tmp_path: pathlib.Path) -> None: |
| 92 | r = _invoke(tmp_path, ["hooks", "list", "--json"]) |
| 93 | assert r.exit_code == 2 |
File History
1 commit
sha256:4802c0281407b5de70ce1b34bbe1d1cd055533cbd4b8be35861a6a3eb545c1dc
feat(#192): Phase 1 — .musehooks.toml parser + `muse hooks list`
Sonnet 5
patch
3 days ago