fix(tests): isolate the developer's real .env from the test suite (#329)

get_effective_config() runs load_dotenv(find_dotenv(usecwd=True),
override=True), so any test that loads config injected the repo's real
.env into os.environ for the rest of the pytest process. An
empty-valued line like MINIMAX_BASE_URL= then made
os.environ.get(key, default) return '' instead of the default,
failing the MiniMax routing tests in full-suite runs while they
passed in isolation.

Generalizes the find_dotenv redirect that test_config.py's
temp_config_dir fixture already applied locally into a suite-wide
autouse fixture, pointing at a never-created path so tests writing
their own tmp_path/.env cannot collide with it. Adds a regression
test reproducing the leak.

Co-authored-by: Xi Zhang <106144707+X-iZhang@users.noreply.github.com>
This commit is contained in:
Mani Saint-Victor
2026-07-13 06:27:40 -04:00
committed by GitHub
parent f72f7b93d5
commit f3e65a446f
2 changed files with 52 additions and 5 deletions
+25
View File
@@ -1,7 +1,11 @@
"""Shared fixtures for EvoScientist tests."""
from pathlib import Path
import pytest
_NONEXISTENT_DOTENV = str(Path(__file__).with_name(".pytest-dotenv-does-not-exist"))
@pytest.fixture(autouse=True)
def _reset_tool_selection_state():
@@ -164,3 +168,24 @@ def restore_model_passthrough_patch():
yield
finally:
_reset()
@pytest.fixture(autouse=True)
def _isolate_dotenv(monkeypatch):
"""Keep the developer's real .env out of the test environment.
``get_effective_config`` runs ``load_dotenv(find_dotenv(usecwd=True),
override=True)``, so any test that loads config injects the repo's
real .env into ``os.environ`` for the rest of the pytest process.
An empty-valued line like ``MINIMAX_BASE_URL=`` then makes
``os.environ.get(key, default)`` return "" instead of the default,
breaking unrelated tests later in the run (see issue #322).
Pointing ``find_dotenv`` at a fixed path that does not exist makes
``load_dotenv`` a no-op without creating a temporary directory for
every test.
"""
monkeypatch.setattr(
"EvoScientist.config.settings.find_dotenv",
lambda *args, **kwargs: _NONEXISTENT_DOTENV,
)
+27 -5
View File
@@ -53,11 +53,6 @@ def temp_config_dir(tmp_path, monkeypatch):
"""Use a temporary directory for config during tests."""
config_dir = tmp_path / "evoscientist"
monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path))
# Prevent load_dotenv from loading the project's real .env file
monkeypatch.setattr(
"EvoScientist.config.settings.find_dotenv",
lambda *a, **k: str(tmp_path / ".env"),
)
# Also clear any API keys from environment
for key in [
"ANTHROPIC_API_KEY",
@@ -930,3 +925,30 @@ def test_scheduler_config_defaults_and_env(monkeypatch):
assert eff2.memory_skill_synthesis_mode == MemorySkillSynthesisMode.AUTO
assert eff2.memory_skill_synthesis_cadence == MemorySkillSynthesisCadence.MONTHLY
assert eff2.memory_skill_synthesis_time == "04:30"
# =============================================================================
# Dotenv isolation (issue #322)
# =============================================================================
class TestDotenvIsolation:
def test_env_file_not_leaked_into_process_env(self, tmp_path, monkeypatch):
"""A .env in cwd must not leak into os.environ during tests.
Without the suite-wide ``_isolate_dotenv`` fixture,
``get_effective_config`` loads the developer's real .env with
``override=True``; an empty-valued line like ``MINIMAX_BASE_URL=``
then poisons ``os.environ.get(key, default)`` lookups for every
test that runs afterwards in the same process.
"""
monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path))
repro_dir = tmp_path / "repro"
repro_dir.mkdir()
(repro_dir / ".env").write_text("MINIMAX_BASE_URL=\n")
monkeypatch.chdir(repro_dir)
monkeypatch.delenv("MINIMAX_BASE_URL", raising=False)
get_effective_config()
assert "MINIMAX_BASE_URL" not in os.environ