mirror of
https://github.com/safishamsi/graphify.git
synced 2026-08-27 08:46:43 +00:00
test: sandbox HOME for the whole suite so installers can't touch real config (#2168)
Several test files call install/uninstall functions that operate on the real user home (~/.claude, ~/.gemini, ~/.codebuddy, ~/.copilot), so running the suite deleted/overwrote the developer's actual config. An autouse conftest fixture now points HOME/USERPROFILE/LOCALAPPDATA at a throwaway dir and clears CLAUDE_CONFIG_DIR/XDG_CONFIG_HOME for every test. Supersedes the per-file sandbox proposed in #2057 (thanks @erlandl4g for surfacing it). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
33aa89c722
commit
c18ec81741
@@ -1,9 +1,28 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _sandbox_home(tmp_path_factory, monkeypatch):
|
||||
"""Every test gets a throwaway HOME so installers/uninstallers can never
|
||||
touch the developer's real ~/.claude, ~/.gemini, ~/.codebuddy, ~/.copilot,
|
||||
~/.config, ~/.agents (issue #2168).
|
||||
|
||||
Allocated via tmp_path_factory (not inside tmp_path) so tests that assert
|
||||
the exact contents of their own tmp_path are unaffected."""
|
||||
home = tmp_path_factory.mktemp("sandbox-home")
|
||||
monkeypatch.setenv("HOME", str(home))
|
||||
monkeypatch.setenv("USERPROFILE", str(home)) # Windows ntpath.expanduser
|
||||
monkeypatch.setenv("LOCALAPPDATA", str(home / "AppData" / "Local"))
|
||||
monkeypatch.delenv("CLAUDE_CONFIG_DIR", raising=False) # escape hatch that bypasses Path.home
|
||||
monkeypatch.delenv("XDG_CONFIG_HOME", raising=False)
|
||||
monkeypatch.setattr(Path, "home", classmethod(lambda cls: home))
|
||||
return home
|
||||
|
||||
_ANALYZE_WARNING_FILTERS = (
|
||||
"ignore:Tensorflow not installed; ParametricUMAP will be unavailable:ImportWarning:umap",
|
||||
"ignore:Please import `random` from the `scipy\\.sparse` namespace.*:"
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
"""Regression tests for the repo-wide HOME sandbox (issue #2168).
|
||||
|
||||
The autouse ``_sandbox_home`` fixture in conftest.py must point every
|
||||
home-resolution mechanism at a throwaway directory, so installers and
|
||||
uninstallers exercised by the suite can never delete or rewrite the
|
||||
developer's real ~/.claude, ~/.gemini, ~/.codebuddy, ~/.copilot, etc.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from graphify.__main__ import claude_uninstall
|
||||
|
||||
# Module import happens during collection, before any fixture runs, so this
|
||||
# captures the developer's actual home directory for comparison below.
|
||||
_REAL_HOME = Path(os.path.realpath(os.path.expanduser("~")))
|
||||
|
||||
|
||||
def test_path_home_is_sandboxed(tmp_path_factory):
|
||||
home = Path(os.path.realpath(Path.home()))
|
||||
assert home != _REAL_HOME
|
||||
assert not home.is_relative_to(_REAL_HOME / ".claude")
|
||||
assert home.name.startswith("sandbox-home")
|
||||
basetemp = Path(os.path.realpath(tmp_path_factory.getbasetemp()))
|
||||
assert home.is_relative_to(basetemp)
|
||||
|
||||
|
||||
def test_expanduser_is_sandboxed(tmp_path_factory):
|
||||
expanded = Path(os.path.realpath(os.path.expanduser("~")))
|
||||
assert expanded != _REAL_HOME
|
||||
basetemp = Path(os.path.realpath(tmp_path_factory.getbasetemp()))
|
||||
assert expanded.is_relative_to(basetemp)
|
||||
|
||||
|
||||
def test_claude_config_dir_escape_hatch_is_cleared():
|
||||
assert "CLAUDE_CONFIG_DIR" not in os.environ
|
||||
assert "XDG_CONFIG_HOME" not in os.environ
|
||||
|
||||
|
||||
def test_global_uninstall_is_captured_by_sandbox(tmp_path, tmp_path_factory):
|
||||
"""claude_uninstall deletes the *global* ~/.claude/skills/graphify tree.
|
||||
|
||||
Plant that tree inside the sandbox home, run uninstall against an
|
||||
unrelated project dir, and prove the delete landed in the sandbox
|
||||
(and therefore not in the developer's real home).
|
||||
"""
|
||||
skill = Path.home() / ".claude" / "skills" / "graphify" / "SKILL.md"
|
||||
skill.parent.mkdir(parents=True)
|
||||
skill.write_text("# graphify skill (sandbox copy)\n", encoding="utf-8")
|
||||
|
||||
project_dir = tmp_path / "some-project"
|
||||
project_dir.mkdir()
|
||||
claude_uninstall(project_dir)
|
||||
|
||||
assert not skill.exists(), "global skill delete was not captured by the sandbox"
|
||||
# And the sandbox home itself is still inside pytest's tmp area.
|
||||
assert Path.home().is_relative_to(tmp_path_factory.getbasetemp())
|
||||
Reference in New Issue
Block a user