@@ -25,11 +25,26 @@ def home(tmp_path, monkeypatch):
2525
2626@pytest .fixture
2727def working_model (monkeypatch ):
28- """Mock verify_model() to a verified pass, so install proceeds past the gate."""
29- ok = reqmod .Requirement ("model" , True , True , "mock model verified" )
30- monkeypatch .setattr (reqmod , "verify_model" , lambda ** kw : ok )
31- # doctor and setup import verify_model from this module at call time
32- return ok
28+ """Make install proceed past the strict gate, regardless of what's installed on
29+ the test runner. We mock BOTH verify_model() AND collect(): a bare CI runner has
30+ no `claude` CLI (a REQUIRED check), so mocking only the model still leaves the
31+ gate tripped and every post-gate test fails (this is what CI caught). Mocking
32+ collect() to return all-passing required reqs makes these tests hermetic — they
33+ exercise post-gate install behavior, not the host's environment."""
34+ ok_model = reqmod .Requirement ("model" , True , True , "mock model verified" )
35+ monkeypatch .setattr (reqmod , "verify_model" , lambda ** kw : ok_model )
36+
37+ def _all_pass (* , api_key = None , pool = False ):
38+ return [
39+ reqmod .Requirement ("python" , True , True , "mock python ok" ),
40+ reqmod .Requirement ("claude-cli" , True , True , "mock claude CLI ok" ),
41+ ok_model ,
42+ reqmod .Requirement ("git" , True , False , "mock git ok" ),
43+ reqmod .Requirement ("signing" , True , False , "mock signing ok" ),
44+ ]
45+ monkeypatch .setattr (reqmod , "collect" , _all_pass )
46+ # setup.py imports these names from reqmod at call time, so patching reqmod is enough
47+ return ok_model
3348
3449
3550def _settings (home ):
@@ -83,11 +98,16 @@ def test_install_creates_hooks_and_config(home, working_model):
8398
8499
85100def test_install_uses_absolute_python_path (home , working_model ):
101+ import os
86102 setup .install ()
87103 s = _settings (home )
88104 cmd = next (h ["command" ] for e in s ["hooks" ]["SessionStart" ] for h in e ["hooks" ]
89105 if "komi.adapters" in h ["command" ])
90- assert cmd .split ()[0 ] not in ("python" , "python3" , '"python"' )
106+ # the contract is an ABSOLUTE interpreter path (so the hook can't break on a
107+ # PATH mismatch), not merely "not the literal string python". Strip surrounding
108+ # quotes the command may add for paths-with-spaces, then require absoluteness.
109+ interp = cmd .split (" -m " )[0 ].strip ().strip ('"' )
110+ assert os .path .isabs (interp ), f"hook interpreter not absolute: { interp !r} "
91111
92112
93113def test_install_merges_not_clobbers (home , working_model ):
@@ -122,7 +142,10 @@ def test_install_self_heals_stale_hook_command(home, working_model):
122142 cmds = [h ["command" ] for e in s ["hooks" ]["SessionStart" ] for h in e ["hooks" ]
123143 if "komi.adapters" in h ["command" ]]
124144 assert len (cmds ) == 1
125- assert cmds [0 ].split ()[0 ] not in ("python" , "python3" )
145+ # the stale bare-`python` command must have been upgraded to an absolute path
146+ import os
147+ interp = cmds [0 ].split (" -m " )[0 ].strip ().strip ('"' )
148+ assert os .path .isabs (interp ), f"stale command not healed to absolute path: { interp !r} "
126149
127150
128151def test_install_stores_api_key (home , working_model ):
0 commit comments