Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,26 @@ def _isolate_ucode_state(tmp_path, monkeypatch):
"""Redirect ucode's state file and APP_DIR to a per-test tmp dir.

Defense in depth: even if an individual test forgets to patch save_state,
it can never touch the developer's real ~/.ucode/state.json.
it can never touch the developer's real ~/.ucode/state.json or invoke the
privileged writer for an OS-managed agent config.
"""
import ucode.config_io as config_io_mod
import ucode.databricks as databricks_mod
import ucode.managed_files as managed_files_mod
import ucode.state as state_mod

state_dir = tmp_path / ".ucode"
state_dir.mkdir()
monkeypatch.setattr(state_mod, "STATE_PATH", state_dir / "state.json")
monkeypatch.setattr(config_io_mod, "APP_DIR", state_dir)

def reject_privileged_write(path, _desired_text):
pytest.fail(
f"test attempted a privileged managed-config write to {path}; "
"mock the agent's managed path and writer"
)

monkeypatch.setattr(managed_files_mod, "_sudo_replace", reject_privileged_write)
# Isolate the managed-config opt-in from the developer's own shell: leaving it set changes what
# `ucode`/`ucode configure` do mid-test. Tests that exercise the managed path set it explicitly.
monkeypatch.delenv("ENABLE_MANAGED_AGENT_CONFIG", raising=False)
Expand Down
12 changes: 11 additions & 1 deletion tests/test_managed_resolve.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,11 +230,19 @@ class TestStateFileIsNotRewritten:

@pytest.fixture
def real_state_file(self, tmp_path, monkeypatch):
"""Redirect state.json and the claude settings file into tmp_path, unstubbed."""
"""Redirect state.json and both Claude settings files into tmp_path, unstubbed."""
monkeypatch.setattr(config_io, "APP_DIR", tmp_path)
monkeypatch.setattr(state_mod, "STATE_PATH", tmp_path / "state.json")
monkeypatch.setattr(claude, "CLAUDE_SETTINGS_PATH", tmp_path / "ucode-settings.json")
monkeypatch.setattr(claude, "CLAUDE_BACKUP_PATH", tmp_path / "backup.json")
managed_settings_path = tmp_path / "managed-settings.json"
monkeypatch.setattr(claude, "_managed_settings_path", lambda: managed_settings_path)

def write_managed_file(path, desired_text, *, display):
path.write_text(desired_text, encoding="utf-8")
return "written"

monkeypatch.setattr(claude, "write_managed_file", write_managed_file)
# Seed a developer whose own opus choice differs from the manifest's.
state_mod.save_state(
{
Expand Down Expand Up @@ -268,6 +276,8 @@ def test_settings_file_gets_the_managed_model(self, real_state_file):

env = json.loads((real_state_file / "ucode-settings.json").read_text())["env"]
assert env["ANTHROPIC_DEFAULT_OPUS_MODEL"].startswith("system.ai.claude-opus-5")
managed_env = json.loads((real_state_file / "managed-settings.json").read_text())["env"]
assert managed_env["ANTHROPIC_DEFAULT_OPUS_MODEL"].startswith("system.ai.claude-opus-5")

def test_overlay_bookkeeping_never_lands_on_disk(self, real_state_file):
resolved_state = resolve_state(MANAGED, state_mod.load_state(), "claude")
Expand Down
Loading