"""Per-workspace mode resolution: each workspace keeps its own routing / auto-run mode, falling back to the global default when unset. Exercises AppContext.project_routing_mode / set_project_routing_mode / project_confirm_commands / set_project_auto_run against a temp projects dir. """ from __future__ import annotations import copy import pytest from cowork_local.config import DEFAULT_CONFIG, AppConfig from cowork_local.core import projects as projects_mod from cowork_local.core.projects import Project, new_project, save_project from cowork_local.state import AppContext @pytest.fixture() def ctx(tmp_path, monkeypatch): # Redirect the projects store to a temp dir so load/save hit tmp, not $HOME. monkeypatch.setattr(projects_mod, "PROJECTS_DIR", tmp_path / "projects") data = copy.deepcopy(DEFAULT_CONFIG) cfg = AppConfig(data=data, path=tmp_path / "config.json") return AppContext(cfg) def _mk(ctx, name): return new_project(name, directory=projects_mod.PROJECTS_DIR) def test_defaults_follow_global_when_no_override(ctx): a = _mk(ctx, "Alpha") ctx.active_project_id = a.project_id # Global default switch_mode is "auto" (Off was removed). assert ctx.project_routing_mode("cowork") == "auto" # Change the GLOBAL default → project with no override follows it. ctx.config.data["routing"]["switch_mode"] = "manual" assert ctx.project_routing_mode("cowork") == "manual" def test_legacy_off_and_fallback_resolve_to_auto(ctx): a = _mk(ctx, "Alpha") ctx.active_project_id = a.project_id ctx.config.data["routing"]["switch_mode"] = "manual" for legacy in ("off", "fallback"): a.routing_modes = {"cowork": legacy} save_project(a) assert ctx.project_routing_mode("cowork") == "auto" ctx.set_project_routing_mode("cowork", "off") assert ctx.project_routing_mode("cowork") == "auto" def test_per_workspace_routing_is_isolated(ctx): a = _mk(ctx, "Alpha") b = _mk(ctx, "Beta") ctx.config.data["routing"]["switch_mode"] = "manual" ctx.active_project_id = a.project_id ctx.set_project_routing_mode("cowork", "auto") assert ctx.project_routing_mode("cowork") == "auto" # Switching to workspace B must NOT see A's override (falls back to global). ctx.active_project_id = b.project_id assert ctx.project_routing_mode("cowork") == "manual" # B sets its own, independently. ctx.set_project_routing_mode("cowork", "auto") ctx.active_project_id = a.project_id ctx.set_project_routing_mode("cowork", "manual") ctx.active_project_id = b.project_id assert ctx.project_routing_mode("cowork") == "auto" # A keeps its own. ctx.active_project_id = a.project_id assert ctx.project_routing_mode("cowork") == "manual" def test_per_surface_isolated_within_a_workspace(ctx): a = _mk(ctx, "Alpha") ctx.active_project_id = a.project_id ctx.set_project_routing_mode("cowork", "auto") ctx.set_project_routing_mode("ai_edit", "manual") # co4e untouched → global default. assert ctx.project_routing_mode("cowork") == "auto" assert ctx.project_routing_mode("ai_edit") == "manual" ctx.config.data["routing"]["switch_mode"] = "manual" assert ctx.project_routing_mode("co4e") == "manual" def test_routing_mode_persists_to_disk(ctx): a = _mk(ctx, "Alpha") ctx.active_project_id = a.project_id ctx.set_project_routing_mode("co4e", "auto") # Reload the project from disk — the override survived. reloaded = projects_mod.load_project(a.project_id, projects_mod.PROJECTS_DIR) assert reloaded.routing_modes.get("co4e") == "auto" def test_auto_run_per_workspace(ctx): a = _mk(ctx, "Alpha") b = _mk(ctx, "Beta") # Global default: cowork_confirm_commands is False → auto-run (no confirm). ctx.active_project_id = a.project_id assert ctx.project_confirm_commands() is False assert ctx.project_auto_run() is True # A: require confirm (auto_run=False). B stays on the global default. ctx.set_project_auto_run(False) assert ctx.project_confirm_commands() is True ctx.active_project_id = b.project_id assert ctx.project_confirm_commands() is False # B unaffected by A def test_auto_run_none_follows_global(ctx): a = _mk(ctx, "Alpha") ctx.active_project_id = a.project_id # Turn the GLOBAL confirm setting on; project override is None → follows it. ctx.config.data["agent_security"]["cowork_confirm_commands"] = True assert ctx.project_confirm_commands() is True # Explicit per-project auto-run overrides the global. ctx.set_project_auto_run(True) # auto-approve assert ctx.project_confirm_commands() is False def test_no_active_project_uses_global(ctx): # active_project_id points at a non-existent project → global fallback. ctx.active_project_id = "does-not-exist" ctx.config.data["routing"]["switch_mode"] = "manual" assert ctx.project_routing_mode("cowork") == "manual" # Setting a mode with no real project writes the GLOBAL setting. ctx.set_project_routing_mode("cowork", "auto") assert ctx.config.routing_mode_for("cowork") == "auto" def test_project_dataclass_defaults(): # New fields have safe defaults and round-trip through asdict/load. p = Project(project_id="x", name="X") assert p.routing_modes == {} assert p.auto_run is None