gabriel / muse public
test_hooks_clone_status_notice.py python
164 lines 6.0 KB
Raw
sha256:b5a46a9923166b1435c3d7549801a23fbf281f8f78c766142e923b35c23bdc13 feat(#192): Phase 4 — discoverability nudges at clone/status time Sonnet 5 patch 6 days ago
1 """Tests for musehub#192 Phase 4 — discoverability of undeclared/uninstalled
2 hooks at clone time (`muse clone`) and on-demand (`muse status`).
3
4 Covers the shared `muse.core.hooks.install_notice()` helper directly (real
5 filesystem, no mocking needed) plus its two call sites.
6 """
7
8 from __future__ import annotations
9
10 import os
11 import pathlib
12 from unittest.mock import MagicMock, patch
13
14 import pytest
15
16 from muse.core.hooks import install_hooks, install_notice
17 from tests.cli_test_helper import CliRunner, InvokeResult
18
19 runner = CliRunner()
20
21
22 def _invoke_status(path: pathlib.Path, args: list[str]) -> InvokeResult:
23 saved = os.getcwd()
24 try:
25 os.chdir(path)
26 return runner.invoke(None, args)
27 finally:
28 os.chdir(saved)
29
30
31 def _init_repo(path: pathlib.Path) -> None:
32 r = _invoke_status(path, ["init"])
33 assert r.exit_code == 0, r.output
34
35
36 def _write_hooks(path: pathlib.Path, content: str) -> None:
37 (path / ".musehooks.toml").write_text(content, encoding="utf-8")
38
39
40 # ---------------------------------------------------------------------------
41 # install_notice — shared helper, unit-tested directly
42 # ---------------------------------------------------------------------------
43
44
45 class TestInstallNotice:
46 def test_none_when_not_defined(self, tmp_path: pathlib.Path) -> None:
47 assert install_notice(tmp_path) is None
48
49 def test_message_when_defined_not_installed(self, tmp_path: pathlib.Path) -> None:
50 _write_hooks(tmp_path, '[pre-commit]\ncommands = ["echo hi"]\n')
51 notice = install_notice(tmp_path)
52 assert notice is not None
53 assert "muse hooks install" in notice
54
55 def test_none_when_installed(self, tmp_path: pathlib.Path) -> None:
56 _write_hooks(tmp_path, '[pre-commit]\ncommands = ["echo hi"]\n')
57 install_hooks(tmp_path)
58 assert install_notice(tmp_path) is None
59
60
61 # ---------------------------------------------------------------------------
62 # HK_31 — muse status prints the notice
63 # ---------------------------------------------------------------------------
64
65
66 class TestStatusNotice:
67 def test_status_prints_notice_when_defined_not_installed(self, tmp_path: pathlib.Path) -> None:
68 _init_repo(tmp_path)
69 _write_hooks(tmp_path, '[pre-commit]\ncommands = ["echo hi"]\n')
70 r = _invoke_status(tmp_path, ["status"])
71 assert "muse hooks install" in r.stderr
72
73 def test_status_silent_when_no_hooks_defined(self, tmp_path: pathlib.Path) -> None:
74 _init_repo(tmp_path)
75 r = _invoke_status(tmp_path, ["status"])
76 assert "muse hooks install" not in r.stderr
77
78 def test_status_silent_when_already_installed(self, tmp_path: pathlib.Path) -> None:
79 _init_repo(tmp_path)
80 _write_hooks(tmp_path, '[pre-commit]\ncommands = ["echo hi"]\n')
81 _invoke_status(tmp_path, ["hooks", "install"])
82 r = _invoke_status(tmp_path, ["status"])
83 assert "muse hooks install" not in r.stderr
84
85 def test_status_json_mode_still_silent_on_stdout(self, tmp_path: pathlib.Path) -> None:
86 _init_repo(tmp_path)
87 _write_hooks(tmp_path, '[pre-commit]\ncommands = ["echo hi"]\n')
88 r = _invoke_status(tmp_path, ["status", "--json"])
89 assert "muse hooks install" not in r.output
90
91
92 # ---------------------------------------------------------------------------
93 # HK_30 — muse clone prints the notice for a repo that ships .musehooks.toml
94 # ---------------------------------------------------------------------------
95
96
97 def _make_transport_mock() -> MagicMock:
98 from muse.core.mpack import RemoteInfo
99 from muse.core.types import long_id
100
101 t = MagicMock()
102 commit_id = long_id("a" * 64)
103 t.fetch_remote_info.return_value = RemoteInfo(
104 repo_id="test-repo-id",
105 domain="code",
106 default_branch="main",
107 branch_heads={"main": commit_id},
108 branch_snapshot_ids={"main": long_id("b" * 64)},
109 )
110 t.fetch_mpack.return_value = MagicMock(mpack_bytes=b"", warnings=[])
111 return t
112
113
114 def _make_apply_result():
115 from muse.core.mpack import ApplyResult
116 return ApplyResult(
117 commits_written=1,
118 snapshots_written=1,
119 blobs_written=0,
120 blobs_skipped=0,
121 tags_written=0,
122 failed_blobs=[],
123 skipped_snapshots=[],
124 )
125
126
127 class TestCloneNotice:
128 def _clone(self, target: pathlib.Path, *, write_hooks_file: bool):
129 from muse.core.types import long_id
130
131 commit_id = long_id("a" * 64)
132 snap_id = long_id("b" * 64)
133
134 def _fake_restore(root, cid):
135 if write_hooks_file:
136 (root / ".musehooks.toml").write_text(
137 '[pre-commit]\ncommands = ["echo hi"]\n', encoding="utf-8"
138 )
139
140 with (
141 patch("muse.cli.commands.clone.get_signing_identity", return_value=None),
142 patch("muse.cli.commands.clone.make_transport", return_value=_make_transport_mock()),
143 patch("muse.cli.commands.clone.apply_mpack", return_value=_make_apply_result()),
144 patch("muse.cli.commands.clone.set_remote"),
145 patch("muse.cli.commands.clone.set_remote_head"),
146 patch("muse.cli.commands.clone.set_upstream"),
147 patch("muse.cli.commands.clone._restore_working_tree", side_effect=_fake_restore),
148 patch("muse.cli.commands.clone.read_commit", return_value=MagicMock(snapshot_id=snap_id)),
149 patch("muse.cli.commands.clone.read_snapshot", return_value=MagicMock(manifest={})),
150 ):
151 return runner.invoke(
152 None,
153 ["clone", "http://localhost:19999/gabriel/repo", str(target)],
154 )
155
156 def test_clone_prints_notice_when_repo_ships_hooks(self, tmp_path: pathlib.Path) -> None:
157 target = tmp_path / "cloned-with-hooks"
158 r = self._clone(target, write_hooks_file=True)
159 assert "muse hooks install" in r.stderr
160
161 def test_clone_silent_when_repo_has_no_hooks(self, tmp_path: pathlib.Path) -> None:
162 target = tmp_path / "cloned-no-hooks"
163 r = self._clone(target, write_hooks_file=False)
164 assert "muse hooks install" not in r.stderr
File History 1 commit
sha256:b5a46a9923166b1435c3d7549801a23fbf281f8f78c766142e923b35c23bdc13 feat(#192): Phase 4 — discoverability nudges at clone/status time Sonnet 5 patch 6 days ago