from __future__ import annotations import asyncio import stat from pathlib import Path from types import SimpleNamespace from unittest.mock import MagicMock import pytest from peachy.console import LocalStream from peachy.state import resolve_state_root def _mode(path: Path) -> int: return stat.S_IMODE(path.stat().st_mode) def test_state_root_precedence_and_permissions(tmp_path: Path) -> None: explicit = tmp_path / "explicit" xdg = tmp_path / "xdg" home = tmp_path / "home" result = resolve_state_root( environ={"PEACHY_STATE_DIR": str(explicit), "XDG_STATE_HOME": str(xdg)}, home=home, ) assert result == explicit assert _mode(result) == 0o700 def test_state_root_uses_xdg_then_home(tmp_path: Path) -> None: xdg = tmp_path / "xdg" home = tmp_path / "home" assert resolve_state_root(environ={"XDG_STATE_HOME": str(xdg)}, home=home) == xdg / "peachy" assert resolve_state_root(environ={}, home=home) == home / ".local" / "state" / "peachy" @pytest.mark.parametrize( "environ", [ {"PEACHY_STATE_DIR": "relative/path"}, {"XDG_STATE_HOME": "relative/path"}, ], ) def test_state_root_rejects_relative_overrides(environ: dict[str, str], tmp_path: Path) -> None: with pytest.raises(ValueError, match="absolute"): resolve_state_root(environ=environ, home=tmp_path) def test_persisted_env_is_owner_only(tmp_path: Path) -> None: handler = MagicMock() handler.output_queue = asyncio.Queue() handler.last_activity_time = 0.0 robot = SimpleNamespace(media=SimpleNamespace(audio=None, backend=None)) stream = LocalStream(handler, robot, instance_path=str(tmp_path)) stream._persist_env_values({"OPENAI_API_KEY": "sk-test-file-mode"}) assert _mode(tmp_path / ".env") == 0o600