"""Write-safety guardrails: toggle, dry-run, audit log.""" import asyncio import json import pytest from familiar import writes @pytest.fixture def tmp_data(tmp_path, monkeypatch): monkeypatch.setenv("FAMILIAR_DATA_DIR", str(tmp_path)) monkeypatch.delenv("FAMILIAR_ENABLE_WRITES", raising=False) return tmp_path async def _exec(): return {"ok": True} def test_disabled_by_default(tmp_data): assert writes.writes_enabled() is False def test_toggle_on_disk(tmp_data): writes.set_writes(True) assert writes.writes_enabled() is True writes.set_writes(False) assert writes.writes_enabled() is False def test_env_override(tmp_data, monkeypatch): monkeypatch.setenv("FAMILIAR_ENABLE_WRITES", "true") assert writes.writes_enabled() is True monkeypatch.setenv("FAMILIAR_ENABLE_WRITES", "0") assert writes.writes_enabled() is False def test_dry_run_does_not_execute(tmp_data): ran = [] async def execute(): ran.append(1) return "done" result = asyncio.run(writes.guard("send_mail", {"to": 1}, confirm=False, execute=execute)) assert result["dry_run"] is True assert not ran # never executed def test_confirm_but_disabled_raises(tmp_data): with pytest.raises(writes.WriteDisabled): asyncio.run(writes.guard("send_mail", {}, confirm=True, execute=_exec)) def test_confirm_enabled_executes_and_audits(tmp_data): writes.set_writes(True) result = asyncio.run(writes.guard("send_mail", {"subject": "hi"}, confirm=True, execute=_exec)) assert result["executed"] is True assert result["result"] == {"ok": True} lines = writes.audit_log_path().read_text(encoding="utf-8").strip().splitlines() entry = json.loads(lines[-1]) assert entry["action"] == "send_mail" assert "ts" in entry