"""Tests for muse/core/hooks.py — local activation (musehub#192 Phase 2: HK_10-13). Covers install_hooks / uninstall_hooks / get_status. Installed state lives in .muse/hooks-installed.toml (local only, never shared via clone) — distinct from .musehooks.toml (tracked, shared) which is covered by test_core_hooks.py. """ import pathlib from muse.core.hooks import ( get_status, install_hooks, is_installed, uninstall_hooks, ) from muse.core.paths import hooks_installed_toml_path, muse_dir def _setup_repo_with_hooks(tmp_path: pathlib.Path, content: str | None = None) -> None: muse_dir(tmp_path).mkdir(parents=True, exist_ok=True) if content is not None: (tmp_path / ".musehooks.toml").write_text(content, encoding="utf-8") # --------------------------------------------------------------------------- # HK_10 — install_hooks # --------------------------------------------------------------------------- class TestInstallHooks: def test_install_writes_local_marker(self, tmp_path: pathlib.Path) -> None: _setup_repo_with_hooks(tmp_path, '[pre-commit]\ncommands = ["echo hi"]\n') install_hooks(tmp_path) assert hooks_installed_toml_path(tmp_path).exists() def test_install_makes_is_installed_true(self, tmp_path: pathlib.Path) -> None: _setup_repo_with_hooks(tmp_path, '[pre-commit]\ncommands = ["echo hi"]\n') assert is_installed(tmp_path) is False install_hooks(tmp_path) assert is_installed(tmp_path) is True def test_install_without_musehooks_file_still_succeeds(self, tmp_path: pathlib.Path) -> None: # Installing with nothing defined yet is not an error — it just means # there's nothing to run until .musehooks.toml is added later. _setup_repo_with_hooks(tmp_path, content=None) install_hooks(tmp_path) assert is_installed(tmp_path) is True # --------------------------------------------------------------------------- # HK_11 — uninstall_hooks # --------------------------------------------------------------------------- class TestUninstallHooks: def test_uninstall_clears_marker(self, tmp_path: pathlib.Path) -> None: _setup_repo_with_hooks(tmp_path, '[pre-commit]\ncommands = ["echo hi"]\n') install_hooks(tmp_path) uninstall_hooks(tmp_path) assert is_installed(tmp_path) is False assert not hooks_installed_toml_path(tmp_path).exists() def test_uninstall_when_never_installed_is_a_no_op(self, tmp_path: pathlib.Path) -> None: _setup_repo_with_hooks(tmp_path, content=None) # Must not raise. uninstall_hooks(tmp_path) assert is_installed(tmp_path) is False # --------------------------------------------------------------------------- # HK_12 — get_status (three states) # --------------------------------------------------------------------------- class TestGetStatus: def test_not_defined_when_no_musehooks_file(self, tmp_path: pathlib.Path) -> None: _setup_repo_with_hooks(tmp_path, content=None) status = get_status(tmp_path) assert status.state == "not_defined" def test_not_defined_when_musehooks_file_has_no_commands(self, tmp_path: pathlib.Path) -> None: _setup_repo_with_hooks(tmp_path, "[pre-commit]\n") status = get_status(tmp_path) # An empty commands list still counts as "defined" (the hook point is # declared) — this exercises the boundary distinct from no file at all. assert status.state == "defined_not_installed" def test_defined_not_installed(self, tmp_path: pathlib.Path) -> None: _setup_repo_with_hooks(tmp_path, '[pre-commit]\ncommands = ["echo hi"]\n') status = get_status(tmp_path) assert status.state == "defined_not_installed" def test_installed(self, tmp_path: pathlib.Path) -> None: _setup_repo_with_hooks(tmp_path, '[pre-commit]\ncommands = ["echo hi"]\n') install_hooks(tmp_path) status = get_status(tmp_path) assert status.state == "installed" def test_not_defined_wins_even_if_marker_present(self, tmp_path: pathlib.Path) -> None: # Edge case: hooks were installed, then .musehooks.toml was deleted. # Nothing left to run, regardless of stale local marker. _setup_repo_with_hooks(tmp_path, '[pre-commit]\ncommands = ["echo hi"]\n') install_hooks(tmp_path) (tmp_path / ".musehooks.toml").unlink() status = get_status(tmp_path) assert status.state == "not_defined" def test_status_exposes_parsed_hooks_file(self, tmp_path: pathlib.Path) -> None: _setup_repo_with_hooks(tmp_path, '[pre-commit]\ncommands = ["echo hi"]\n') status = get_status(tmp_path) assert status.hooks_file.hooks["pre-commit"] == ["echo hi"] # --------------------------------------------------------------------------- # HK_13 — idempotency # --------------------------------------------------------------------------- class TestIdempotency: def test_install_twice_is_a_no_op_not_an_error(self, tmp_path: pathlib.Path) -> None: _setup_repo_with_hooks(tmp_path, '[pre-commit]\ncommands = ["echo hi"]\n') install_hooks(tmp_path) install_hooks(tmp_path) # must not raise assert is_installed(tmp_path) is True def test_uninstall_twice_is_a_no_op_not_an_error(self, tmp_path: pathlib.Path) -> None: _setup_repo_with_hooks(tmp_path, '[pre-commit]\ncommands = ["echo hi"]\n') install_hooks(tmp_path) uninstall_hooks(tmp_path) uninstall_hooks(tmp_path) # must not raise assert is_installed(tmp_path) is False