Files
openworker/tests/test_auto_approve_settings.py
T
Devika Verma 71c786ab45 Feature 2: settings pass - flag exposure, gated picker entry, toggles
The auto_approve flag (and its shadow sibling) become first-class settings
instead of hand-edited TOML, and the Auto-Approve mode entry appears in the
picker only when the flag is on.

Server:
- manager: auto_approve()/auto_approve_shadow() read prefs.json first,
  falling back to the config.toml value a power user may have set; both
  writers persist to prefs. Both stores are user-global, so a cloned repo
  still can't enable either (the 1.5 invariant, unchanged).
- get_settings() exposes both; POST /v1/settings/auto-approve and
  /auto-approve-shadow write them (same shape as context-bar).
- Session builds pass the prefs-backed values into build_engine via new
  optional auto_approve/auto_approve_shadow overrides (None = config value),
  so a Settings flip takes effect on the next session build with no restart.
  Scheduled runs keep reading config only - they are unattended, so the
  live reviewer can never fire there regardless.

GUI:
- Mode picker: the Auto-Approve entry is `gated` - shown when
  getSettings().auto_approve is true, fetched on menu open. A session
  already IN auto-approve always shows its own entry so the current mode
  stays legible even if the flag was later turned off. This replaces the
  TEST-ONLY unconditional entry.
- Settings: AutoApproveCard with the feature toggle and the nested shadow-
  evaluation toggle ("records what it would have decided next to your own
  choice - without changing anything").
- api.ts: ModelSettings.auto_approve/auto_approve_shadow + setters.

Verified live against the running sidecar: flag off hides the entry on an
interactive session, flag on shows it, the Settings toggles round-trip and
persist. tests/test_auto_approve_settings.py (6): defaults, REST round-
trip, restart persistence, config fallback, prefs-beats-config, and the
build_engine override. tsc clean; 111 GUI unit tests pass.
2026-08-12 17:25:12 -07:00

81 lines
3.3 KiB
Python

"""Settings wiring for Auto-Approve (spec §1.5 / Part 6 step 3).
Covers the prefs-backed feature flag + shadow toggle: default off, REST round-trip,
persistence across a manager restart, config.toml fallback, and the build-engine override
so a flag flip takes effect on the next session build without a config edit.
"""
from __future__ import annotations
import pytest
from fastapi.testclient import TestClient
from coworker.server.app import create_app
from coworker.server.manager import SessionManager
@pytest.fixture
def client(tmp_path, monkeypatch):
# Isolate config.toml: no hand-set flag leaking in from the dev machine.
monkeypatch.setenv("COWORKER_STATE_DIR", str(tmp_path / "state"))
return TestClient(create_app(SessionManager(data_dir=tmp_path / "data")))
def test_flags_default_off(client):
s = client.get("/v1/settings").json()
assert s["auto_approve"] is False
assert s["auto_approve_shadow"] is False
def test_set_auto_approve_roundtrip(client):
r = client.post("/v1/settings/auto-approve", json={"auto_approve": True}).json()
assert r["ok"] and r["auto_approve"] is True
assert client.get("/v1/settings").json()["auto_approve"] is True
# Off again.
client.post("/v1/settings/auto-approve", json={"auto_approve": False})
assert client.get("/v1/settings").json()["auto_approve"] is False
def test_shadow_is_independent_of_the_live_flag(client):
client.post("/v1/settings/auto-approve-shadow", json={"auto_approve_shadow": True})
s = client.get("/v1/settings").json()
assert s["auto_approve"] is False # untouched
assert s["auto_approve_shadow"] is True
def test_flags_persist_across_restart(tmp_path, monkeypatch):
monkeypatch.setenv("COWORKER_STATE_DIR", str(tmp_path / "state"))
data_dir = tmp_path / "data"
c1 = TestClient(create_app(SessionManager(data_dir=data_dir)))
c1.post("/v1/settings/auto-approve", json={"auto_approve": True})
reborn = SessionManager(data_dir=data_dir)
assert reborn.auto_approve() is True
assert TestClient(create_app(reborn)).get("/v1/settings").json()["auto_approve"] is True
def test_prefs_falls_back_to_config_when_unset(tmp_path, monkeypatch):
# No prefs key set → the manager reads config.toml. Point config at a file that enables it.
state = tmp_path / "state"
state.mkdir(parents=True)
(state / "config.toml").write_text("auto_approve = true\n")
monkeypatch.setenv("COWORKER_STATE_DIR", str(state))
mgr = SessionManager(data_dir=tmp_path / "data")
assert mgr.auto_approve() is True # config value, no prefs entry
# A prefs write then wins over config.
mgr.set_auto_approve(False)
assert mgr.auto_approve() is False
def test_build_engine_override_beats_config(tmp_path, monkeypatch):
# build_engine's auto_approve arg (what the server passes) overrides the config value.
monkeypatch.setenv("COWORKER_STATE_DIR", str(tmp_path / "state"))
from coworker.agent import build_engine
from coworker.agents.chat import chat_agent
# config has it off by default; override to on → a reviewer is attached.
engine = build_engine(agent=chat_agent(), auto_approve=True, auto_approve_shadow=False)
assert engine.reviewer is not None
engine2 = build_engine(agent=chat_agent(), auto_approve=False, auto_approve_shadow=False)
assert engine2.reviewer is None