diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..5f7da2b --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,43 @@ +"""Keep the test suite away from the developer's real DevOrch data. + +Several modules compute ``Path.home() / ".devorch"`` at import time, and some tests (for example +``Permissions().set_tool_permission(...)``) save through those paths. Without isolation, running +``pytest`` overwrote the developer's real ``~/.devorch/permissions.yaml`` (replacing their saved rules +with ``edit: allow`` / ``filesystem: allow``) and wrote to their real sessions folder. + +This must run before the application modules are imported, hence environment variables set at import +time of this conftest rather than a fixture. +""" + +import os +import shutil +import tempfile + +_TEST_HOME = tempfile.mkdtemp(prefix="devorch-test-home-") + +# Path.home() reads HOME on POSIX and USERPROFILE on Windows +os.environ["HOME"] = _TEST_HOME +os.environ["USERPROFILE"] = _TEST_HOME +os.environ.pop("HOMEDRIVE", None) +os.environ.pop("HOMEPATH", None) +# never read from or write to the developer's real keychain +os.environ["PYTHON_KEYRING_BACKEND"] = "keyring.backends.null.Keyring" +# and no real API keys leak into tests +for _var in ( + "OPENAI_API_KEY", + "ANTHROPIC_API_KEY", + "GOOGLE_API_KEY", + "GROQ_API_KEY", + "MISTRAL_API_KEY", + "OPENROUTER_API_KEY", + "TOGETHER_API_KEY", + "GITHUB_TOKEN", + "DEEPSEEK_API_KEY", + "MOONSHOT_API_KEY", + "CUSTOM_API_KEY", +): + os.environ.pop(_var, None) + + +def pytest_sessionfinish(session, exitstatus): + shutil.rmtree(_TEST_HOME, ignore_errors=True) diff --git a/tests/test_isolation.py b/tests/test_isolation.py new file mode 100644 index 0000000..48b65f7 --- /dev/null +++ b/tests/test_isolation.py @@ -0,0 +1,41 @@ +"""The suite must never read or write the developer's real ~/.devorch.""" + +import os +from pathlib import Path + +from config import permissions as perm_mod +from config import settings as settings_mod +from core import sessions as sessions_mod +from core.memory import MEMORY_DIR + + +def test_home_is_a_throwaway_directory(): + home = Path.home() + assert "devorch-test-home-" in home.name + assert home == Path(os.environ["HOME"]) + + +def test_application_paths_resolve_under_the_test_home(): + home = Path.home() + for path in ( + perm_mod.PERMISSIONS_FILE, + settings_mod.CONFIG_FILE, + sessions_mod.DB_PATH, + MEMORY_DIR, + ): + assert home in path.parents, f"{path} is outside the test HOME" + + +def test_saving_permissions_does_not_touch_the_real_file(): + from config.permissions import PermissionLevel, Permissions + + Permissions().set_tool_permission("edit", PermissionLevel.ALLOW) # saves to PERMISSIONS_FILE + assert perm_mod.PERMISSIONS_FILE.exists() + assert Path.home() in perm_mod.PERMISSIONS_FILE.parents + + +def test_keyring_is_the_null_backend(): + import keyring + + assert keyring.get_password("devorch", "openai") is None + assert type(keyring.get_keyring()).__module__ == "keyring.backends.null"