8Validate the B1.4 ``oct install --editable`` safety wrapper. The
9heart of the command is the preflight matrix; the actual ``pip
10install -e`` subprocess is mocked because we don't want test runs to
11mutate the real venv's site-packages.
15- Cover each preflight refusal: not-an-Option-C-project, inside a
16 worktree, wrong venv, existing shadow install.
17- Cover each override flag (--allow-worktree, --use-active-venv,
18 --reinstall, --allow-outside-project).
19- Cover the success path (preflights green, pip mocked to exit 0,
20 post-install diagnostics printed).
21- Cover the JSON output schema for both refusal and success.
28 L3 -- assertion details
32- Tests use ``CliRunner`` from Click rather than spawning a
33 subprocess. This lets us monkey-patch the subprocess call cleanly.
34- Tests must not modify the developer's actual venv. Every
35 ``subprocess.run`` is patched to a stub that returns
36 ``returncode=0`` without performing any work.
40from pathlib
import Path
41from unittest.mock
import patch, MagicMock
44from click.testing
import CliRunner
55 """Create the minimum-marker Option C project layout."""
56 root.mkdir(parents=
True, exist_ok=
True)
57 (root /
"pyproject.toml").write_text(
58 f
'[project]\nname = "{name}"\n', encoding=
"utf-8",
60 (root /
".octrc.json").write_text(
61 '{"linter": {"profile": "compact"}}',
68 """Create a venv-shaped directory (just the markers we look at)."""
70 venv.mkdir(parents=
True, exist_ok=
True)
71 bin_dir = venv / (
"Scripts" if (venv /
"Scripts").exists()
73 bin_dir.mkdir(exist_ok=
True)
79 return _sys.platform.startswith(
"win")
90 bad = tmp_path /
"not_oc"
92 (bad /
".octrc.json").write_text(
"{}", encoding=
"utf-8")
95 with runner.isolated_filesystem(temp_dir=tmp_path):
96 result = runner.invoke(
97 oct_install_cmd, [
"--editable", str(bad)],
99 assert result.exit_code == 1
100 assert "not an Option C project" in result.output
103 bad = tmp_path /
"no_octrc"
105 (bad /
"pyproject.toml").write_text(
106 '[project]\nname = "x"\n', encoding=
"utf-8",
109 with runner.isolated_filesystem(temp_dir=tmp_path):
110 result = runner.invoke(
111 oct_install_cmd, [
"--editable", str(bad)],
113 assert result.exit_code == 1
114 assert "not an Option C project" in result.output
126 wt_root = tmp_path /
".claude" /
"worktrees" /
"abc123"
129 with runner.isolated_filesystem(temp_dir=tmp_path):
130 result = runner.invoke(
131 oct_install_cmd, [
"--editable", str(target)],
133 assert result.exit_code == 1
134 assert "Claude Code worktree" in result.output
137 wt_root = tmp_path /
".claude" /
"worktrees" /
"abc123"
140 with patch(
"oct.install.oct_install.subprocess.run")
as mock_run, \
142 "oct.install.oct_install._active_venv_root",
146 "oct.install.oct_install._pip_show_location",
149 mock_run.return_value = MagicMock(
150 returncode=0, stdout=
"", stderr=
"",
152 result = runner.invoke(oct_install_cmd, [
153 "--editable", str(target),
154 "--allow-worktree",
"--use-active-venv",
155 "--allow-outside-project",
157 assert result.exit_code == 0, result.output
172 "oct.install.oct_install._active_venv_root",
175 result = runner.invoke(
176 oct_install_cmd, [
"--editable", str(target)],
178 assert result.exit_code == 1
179 assert "no active venv" in result.output
184 wrong_venv = tmp_path /
"elsewhere" /
".venv"
185 wrong_venv.mkdir(parents=
True)
188 "oct.install.oct_install._active_venv_root",
189 return_value=wrong_venv.resolve(),
191 result = runner.invoke(
192 oct_install_cmd, [
"--editable", str(target)],
194 assert result.exit_code == 1
195 assert "active venv is at" in result.output
200 wrong_venv = tmp_path /
"elsewhere" /
".venv"
201 wrong_venv.mkdir(parents=
True)
204 "oct.install.oct_install._active_venv_root",
205 return_value=wrong_venv.resolve(),
207 "oct.install.oct_install._pip_show_location",
210 "oct.install.oct_install.subprocess.run",
212 mock_run.return_value = MagicMock(
213 returncode=0, stdout=
"", stderr=
"",
215 result = runner.invoke(oct_install_cmd, [
216 "--editable", str(target),
217 "--use-active-venv",
"--allow-outside-project",
219 assert result.exit_code == 0, result.output
232 other_worktree = tmp_path /
"other_proj"
233 other_worktree.mkdir()
236 "oct.install.oct_install._active_venv_root",
237 return_value=(target /
".venv").resolve(),
239 "oct.install.oct_install._pip_show_location",
240 return_value=other_worktree.resolve(),
242 result = runner.invoke(oct_install_cmd, [
243 "--editable", str(target),
244 "--allow-outside-project",
246 assert result.exit_code == 1
247 assert "already installed" in result.output
248 assert "silently shadow" in result.output
253 other_worktree = tmp_path /
"other_proj"
254 other_worktree.mkdir()
257 "oct.install.oct_install._active_venv_root",
258 return_value=(target /
".venv").resolve(),
260 "oct.install.oct_install._pip_show_location",
261 return_value=other_worktree.resolve(),
263 "oct.install.oct_install.subprocess.run",
265 mock_run.return_value = MagicMock(
266 returncode=0, stdout=
"", stderr=
"",
268 result = runner.invoke(oct_install_cmd, [
269 "--editable", str(target),
270 "--reinstall",
"--allow-outside-project",
272 assert result.exit_code == 0, result.output
283 self, tmp_path: Path,
289 "oct.install.oct_install._active_venv_root",
290 return_value=(target /
".venv").resolve(),
292 "oct.install.oct_install._pip_show_location",
293 )
as mock_show, patch(
294 "oct.install.oct_install._which_oct",
295 return_value=Path(
"/fake/bin/oct"),
297 "oct.install.oct_install.subprocess.run",
299 mock_run.return_value = MagicMock(
300 returncode=0, stdout=
"", stderr=
"",
303 mock_show.side_effect = [
None, target]
304 result = runner.invoke(oct_install_cmd, [
305 "--editable", str(target),
306 "--allow-outside-project",
308 assert result.exit_code == 0, result.output
309 assert "myproj installed" in result.output
316 "oct.install.oct_install._active_venv_root",
317 return_value=(target /
".venv").resolve(),
319 "oct.install.oct_install._pip_show_location",
320 )
as mock_show, patch(
321 "oct.install.oct_install._which_oct",
322 return_value=Path(
"/fake/bin/oct"),
324 "oct.install.oct_install.subprocess.run",
326 mock_run.return_value = MagicMock(
327 returncode=0, stdout=
"", stderr=
"",
329 mock_show.side_effect = [
None, target]
330 result = runner.invoke(oct_install_cmd, [
331 "--editable", str(target),
332 "--allow-outside-project",
"--json",
334 assert result.exit_code == 0, result.output
335 data = json.loads(result.output)
336 assert data[
"operation"] ==
"install"
337 assert data[
"package"] ==
"myproj"
338 assert data[
"exit_code"] == 0
339 assert "oct_binary" in data
340 assert "installed_location" in data
343 bad = tmp_path /
"not_oc"
346 result = runner.invoke(oct_install_cmd, [
347 "--editable", str(bad),
"--json",
349 assert result.exit_code == 1
350 data = json.loads(result.output)
351 assert data[
"exit_code"] == 1
352 assert data[
"errors"]
353 assert any(
"not an Option C project" in e
for e
in data[
"errors"])
366 result = runner.invoke(oct_install_cmd, [str(target)])
367 assert result.exit_code == 1
368 assert "--editable" in result.output
None test_missing_editable_refuses(self, Path tmp_path)
None test_target_missing_octrc_refuses(self, Path tmp_path)
None test_target_missing_pyproject_refuses(self, Path tmp_path)
None test_shadow_in_other_worktree_refuses(self, Path tmp_path)
None test_shadow_with_reinstall_proceeds(self, Path tmp_path)
None test_no_active_venv_refuses(self, Path tmp_path)
None test_wrong_venv_refuses(self, Path tmp_path)
None test_use_active_venv_skips_check(self, Path tmp_path)
None test_allow_worktree_overrides(self, Path tmp_path)
None test_worktree_target_refuses(self, Path tmp_path)
None test_json_refusal_schema(self, Path tmp_path)
None test_json_success_schema(self, Path tmp_path)
None test_clean_install_succeeds_and_prints_diagnostics(self, Path tmp_path)
Path _make_oc_project(Path root, str name="myproj")
Path _make_venv(Path root)