gabriel / muse public
test_core_hooks_install.py python
131 lines 5.6 KB
Raw
sha256:d4df453a03f8b54af479f32103d9023556b28a0e38292215446959f238f3e418 feat(#192): Phase 2 — muse hooks install/uninstall/status (… Sonnet 5 patch 11 hours ago
1 """Tests for muse/core/hooks.py — local activation (musehub#192 Phase 2: HK_10-13).
2
3 Covers install_hooks / uninstall_hooks / get_status. Installed state lives in
4 .muse/hooks-installed.toml (local only, never shared via clone) — distinct
5 from .musehooks.toml (tracked, shared) which is covered by test_core_hooks.py.
6 """
7
8 import pathlib
9
10 from muse.core.hooks import (
11 get_status,
12 install_hooks,
13 is_installed,
14 uninstall_hooks,
15 )
16 from muse.core.paths import hooks_installed_toml_path, muse_dir
17
18
19 def _setup_repo_with_hooks(tmp_path: pathlib.Path, content: str | None = None) -> None:
20 muse_dir(tmp_path).mkdir(parents=True, exist_ok=True)
21 if content is not None:
22 (tmp_path / ".musehooks.toml").write_text(content, encoding="utf-8")
23
24
25 # ---------------------------------------------------------------------------
26 # HK_10 — install_hooks
27 # ---------------------------------------------------------------------------
28
29
30 class TestInstallHooks:
31 def test_install_writes_local_marker(self, tmp_path: pathlib.Path) -> None:
32 _setup_repo_with_hooks(tmp_path, '[pre-commit]\ncommands = ["echo hi"]\n')
33 install_hooks(tmp_path)
34 assert hooks_installed_toml_path(tmp_path).exists()
35
36 def test_install_makes_is_installed_true(self, tmp_path: pathlib.Path) -> None:
37 _setup_repo_with_hooks(tmp_path, '[pre-commit]\ncommands = ["echo hi"]\n')
38 assert is_installed(tmp_path) is False
39 install_hooks(tmp_path)
40 assert is_installed(tmp_path) is True
41
42 def test_install_without_musehooks_file_still_succeeds(self, tmp_path: pathlib.Path) -> None:
43 # Installing with nothing defined yet is not an error — it just means
44 # there's nothing to run until .musehooks.toml is added later.
45 _setup_repo_with_hooks(tmp_path, content=None)
46 install_hooks(tmp_path)
47 assert is_installed(tmp_path) is True
48
49
50 # ---------------------------------------------------------------------------
51 # HK_11 — uninstall_hooks
52 # ---------------------------------------------------------------------------
53
54
55 class TestUninstallHooks:
56 def test_uninstall_clears_marker(self, tmp_path: pathlib.Path) -> None:
57 _setup_repo_with_hooks(tmp_path, '[pre-commit]\ncommands = ["echo hi"]\n')
58 install_hooks(tmp_path)
59 uninstall_hooks(tmp_path)
60 assert is_installed(tmp_path) is False
61 assert not hooks_installed_toml_path(tmp_path).exists()
62
63 def test_uninstall_when_never_installed_is_a_no_op(self, tmp_path: pathlib.Path) -> None:
64 _setup_repo_with_hooks(tmp_path, content=None)
65 # Must not raise.
66 uninstall_hooks(tmp_path)
67 assert is_installed(tmp_path) is False
68
69
70 # ---------------------------------------------------------------------------
71 # HK_12 — get_status (three states)
72 # ---------------------------------------------------------------------------
73
74
75 class TestGetStatus:
76 def test_not_defined_when_no_musehooks_file(self, tmp_path: pathlib.Path) -> None:
77 _setup_repo_with_hooks(tmp_path, content=None)
78 status = get_status(tmp_path)
79 assert status.state == "not_defined"
80
81 def test_not_defined_when_musehooks_file_has_no_commands(self, tmp_path: pathlib.Path) -> None:
82 _setup_repo_with_hooks(tmp_path, "[pre-commit]\n")
83 status = get_status(tmp_path)
84 # An empty commands list still counts as "defined" (the hook point is
85 # declared) — this exercises the boundary distinct from no file at all.
86 assert status.state == "defined_not_installed"
87
88 def test_defined_not_installed(self, tmp_path: pathlib.Path) -> None:
89 _setup_repo_with_hooks(tmp_path, '[pre-commit]\ncommands = ["echo hi"]\n')
90 status = get_status(tmp_path)
91 assert status.state == "defined_not_installed"
92
93 def test_installed(self, tmp_path: pathlib.Path) -> None:
94 _setup_repo_with_hooks(tmp_path, '[pre-commit]\ncommands = ["echo hi"]\n')
95 install_hooks(tmp_path)
96 status = get_status(tmp_path)
97 assert status.state == "installed"
98
99 def test_not_defined_wins_even_if_marker_present(self, tmp_path: pathlib.Path) -> None:
100 # Edge case: hooks were installed, then .musehooks.toml was deleted.
101 # Nothing left to run, regardless of stale local marker.
102 _setup_repo_with_hooks(tmp_path, '[pre-commit]\ncommands = ["echo hi"]\n')
103 install_hooks(tmp_path)
104 (tmp_path / ".musehooks.toml").unlink()
105 status = get_status(tmp_path)
106 assert status.state == "not_defined"
107
108 def test_status_exposes_parsed_hooks_file(self, tmp_path: pathlib.Path) -> None:
109 _setup_repo_with_hooks(tmp_path, '[pre-commit]\ncommands = ["echo hi"]\n')
110 status = get_status(tmp_path)
111 assert status.hooks_file.hooks["pre-commit"] == ["echo hi"]
112
113
114 # ---------------------------------------------------------------------------
115 # HK_13 — idempotency
116 # ---------------------------------------------------------------------------
117
118
119 class TestIdempotency:
120 def test_install_twice_is_a_no_op_not_an_error(self, tmp_path: pathlib.Path) -> None:
121 _setup_repo_with_hooks(tmp_path, '[pre-commit]\ncommands = ["echo hi"]\n')
122 install_hooks(tmp_path)
123 install_hooks(tmp_path) # must not raise
124 assert is_installed(tmp_path) is True
125
126 def test_uninstall_twice_is_a_no_op_not_an_error(self, tmp_path: pathlib.Path) -> None:
127 _setup_repo_with_hooks(tmp_path, '[pre-commit]\ncommands = ["echo hi"]\n')
128 install_hooks(tmp_path)
129 uninstall_hooks(tmp_path)
130 uninstall_hooks(tmp_path) # must not raise
131 assert is_installed(tmp_path) is False
File History 1 commit
sha256:d4df453a03f8b54af479f32103d9023556b28a0e38292215446959f238f3e418 feat(#192): Phase 2 — muse hooks install/uninstall/status (… Sonnet 5 patch 11 hours ago