Private
Public Access
0
0

test(sandbox): update v3 paths-aware tests for FR1+FR3 invariants

- test_paths.py: explicit initialize_paths(<empty_config>) instead of
  SLOP_CONFIG env var (v3 design); add restore_paths fixture so other
  tests keep their conftest workspace init.
- test_summary_cache.py: use tmp_path (under ./tests/) instead of
  hardcoded Path('.test_cache') that FR1 blocks.
- test_orchestrator_pm_history.py: use tempfile.mkdtemp() instead of
  writing to project-root 'test_conductor/' that FR1 blocks.
- test_gui_paths.py::test_save_paths: mock src.paths.initialize_paths
  instead of src.paths.reset_paths (v3 entry point).

All 12 tests pass in the Tier 2 clone after these fixes.
This commit is contained in:
2026-06-19 12:36:21 -04:00
parent 848b9e293f
commit 63e91198ac
4 changed files with 46 additions and 37 deletions
+2 -2
View File
@@ -26,7 +26,7 @@ def test_save_paths():
with patch('shutil.copy') as mock_copy, \ with patch('shutil.copy') as mock_copy, \
patch('src.paths.get_config_path') as mock_get_cfg, \ patch('src.paths.get_config_path') as mock_get_cfg, \
patch('src.paths.reset_paths') as mock_reset, \ patch('src.paths.initialize_paths') as mock_init_paths, \
patch.object(MockApp, 'init_state') as mock_init: patch.object(MockApp, 'init_state') as mock_init:
mock_get_cfg.return_value = MagicMock() mock_get_cfg.return_value = MagicMock()
@@ -40,5 +40,5 @@ def test_save_paths():
mock_app.save_config.assert_called_once() mock_app.save_config.assert_called_once()
mock_copy.assert_called_once() mock_copy.assert_called_once()
assert 'applied' in mock_app.ai_status assert 'applied' in mock_app.ai_status
mock_reset.assert_called_once() mock_init_paths.assert_called_once()
mock_init.assert_called_once() mock_init.assert_called_once()
+4 -2
View File
@@ -2,14 +2,16 @@ import unittest
from unittest.mock import patch, MagicMock from unittest.mock import patch, MagicMock
import shutil import shutil
import json import json
import tempfile
from pathlib import Path from pathlib import Path
from src import orchestrator_pm from src import orchestrator_pm
from src.result_types import Result from src.result_types import Result
class TestOrchestratorPMHistory(unittest.TestCase): class TestOrchestratorPMHistory(unittest.TestCase):
def setUp(self) -> None: def setUp(self) -> None:
self.test_dir = Path("test_conductor") # v3 paths.py: use tempdir (under system temp) for test data instead of
self.test_dir.mkdir(exist_ok=True) # writing to project-root "test_conductor/" which the FR1 guard blocks.
self.test_dir = Path(tempfile.mkdtemp(prefix="test_orch_"))
self.archive_dir = self.test_dir / "archive" self.archive_dir = self.test_dir / "archive"
self.tracks_dir = self.test_dir / "tracks" self.tracks_dir = self.test_dir / "tracks"
self.archive_dir.mkdir(exist_ok=True) self.archive_dir.mkdir(exist_ok=True)
+32 -16
View File
@@ -1,29 +1,42 @@
import os
import pytest import pytest
from pathlib import Path from pathlib import Path
from src import paths from src import paths
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def reset_paths(): def restore_paths():
paths.reset_paths() # v3 paths.py: PathsConfig is frozen at init time. Save the pre-test
# config path so we can restore after this test (other tests rely on
# the conftest-initialized workspace). The fixtures themselves call
# paths.initialize_paths(<tmp_config>) to control resolution.
pre = paths.get_config_path()
yield yield
paths.reset_paths() paths.initialize_paths(pre)
def test_default_paths(tmp_path, monkeypatch): def test_default_paths(tmp_path):
monkeypatch.setenv("SLOP_CONFIG", str(tmp_path / "non_existent.toml")) # v3 paths.py: when config has no [paths] section, paths come from
# defaults. Pass a config that does NOT define [paths].
root_dir = Path(paths.__file__).resolve().parent.parent root_dir = Path(paths.__file__).resolve().parent.parent
assert paths.get_logs_dir() == root_dir / "logs/sessions" empty_config = tmp_path / "empty.toml"
assert paths.get_scripts_dir() == root_dir / "scripts/generated" empty_config.write_text("# no [paths] section here\n")
# config path should be what we set in env paths.initialize_paths(empty_config)
assert paths.get_config_path() == tmp_path / "non_existent.toml"
assert paths.get_logs_dir() == root_dir / "logs" / "sessions"
assert paths.get_scripts_dir() == root_dir / "scripts" / "generated"
assert paths.get_config_path() == empty_config.resolve()
def test_env_var_overrides(tmp_path, monkeypatch): def test_env_var_overrides(tmp_path, monkeypatch):
# Absolute env var # v3 paths.py: env var wins over config and default. Set env var, then
# init with a config that does NOT define [paths] (so only env + default apply).
abs_logs = (tmp_path / "abs_logs").resolve() abs_logs = (tmp_path / "abs_logs").resolve()
monkeypatch.setenv("SLOP_LOGS_DIR", str(abs_logs)) monkeypatch.setenv("SLOP_LOGS_DIR", str(abs_logs))
empty_config = tmp_path / "empty.toml"
empty_config.write_text("# no [paths] section\n")
paths.initialize_paths(empty_config)
assert paths.get_logs_dir() == abs_logs assert paths.get_logs_dir() == abs_logs
def test_config_overrides(tmp_path, monkeypatch): def test_config_overrides(tmp_path):
# v3 paths.py: [paths] section in config overrides default. Relative
# paths in config are resolved against project root.
root_dir = Path(paths.__file__).resolve().parent.parent root_dir = Path(paths.__file__).resolve().parent.parent
config_file = tmp_path / "custom_config.toml" config_file = tmp_path / "custom_config.toml"
content = """ content = """
@@ -32,12 +45,13 @@ logs_dir = "cfg_logs"
scripts_dir = "cfg_scripts" scripts_dir = "cfg_scripts"
""" """
config_file.write_text(content) config_file.write_text(content)
monkeypatch.setenv("SLOP_CONFIG", str(config_file)) paths.initialize_paths(config_file)
assert paths.get_logs_dir() == root_dir / "cfg_logs" assert paths.get_logs_dir() == root_dir / "cfg_logs"
assert paths.get_scripts_dir() == root_dir / "cfg_scripts" assert paths.get_scripts_dir() == root_dir / "cfg_scripts"
def test_precedence(tmp_path, monkeypatch): def test_precedence(tmp_path, monkeypatch):
# v3 paths.py: env var SLOP_LOGS_DIR wins over [paths] config entry.
root_dir = Path(paths.__file__).resolve().parent.parent root_dir = Path(paths.__file__).resolve().parent.parent
config_file = tmp_path / "custom_config.toml" config_file = tmp_path / "custom_config.toml"
content = """ content = """
@@ -45,11 +59,13 @@ def test_precedence(tmp_path, monkeypatch):
logs_dir = "cfg_logs" logs_dir = "cfg_logs"
""" """
config_file.write_text(content) config_file.write_text(content)
monkeypatch.setenv("SLOP_CONFIG", str(config_file)) # Use absolute env_logs path so _resolve_path returns it as-is.
monkeypatch.setenv("SLOP_LOGS_DIR", "env_logs") env_logs = (root_dir / "env_logs").resolve()
monkeypatch.setenv("SLOP_LOGS_DIR", str(env_logs))
paths.initialize_paths(config_file)
# Env var should take precedence over config # Env var should take precedence over config
assert paths.get_logs_dir() == (root_dir / "env_logs").resolve() assert paths.get_logs_dir() == env_logs
def test_conductor_dir_project_relative(tmp_path): def test_conductor_dir_project_relative(tmp_path):
# Should default to tmp_path/conductor # Should default to tmp_path/conductor
+8 -17
View File
@@ -9,11 +9,10 @@ def test_get_file_hash():
expected = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9" expected = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
assert get_file_hash(content) == expected assert get_file_hash(content) == expected
def test_summary_cache(): def test_summary_cache(tmp_path):
cache_dir = Path(".test_cache") # v3 paths.py: use tmp_path (which is under ./tests/) instead of
if cache_dir.exists(): # hardcoded project-root paths that the FR1 guard blocks.
shutil.rmtree(cache_dir) cache_file = tmp_path / "cache.json"
cache_file = cache_dir / "cache.json"
cache = SummaryCache(str(cache_file)) cache = SummaryCache(str(cache_file))
@@ -35,16 +34,11 @@ def test_summary_cache():
# Test persistence # Test persistence
cache2 = SummaryCache(str(cache_file)) cache2 = SummaryCache(str(cache_file))
assert cache2.get_summary(file_path, content_hash) == summary assert cache2.get_summary(file_path, content_hash) == summary
# Cleanup
if cache_dir.exists():
shutil.rmtree(cache_dir)
def test_summary_cache_lru():
cache_dir = Path(".test_cache_lru") def test_summary_cache_lru(tmp_path):
if cache_dir.exists(): # v3 paths.py: use tmp_path instead of hardcoded project-root paths.
shutil.rmtree(cache_dir) cache_file = tmp_path / "cache.json"
cache_file = cache_dir / "cache.json"
# Create cache with max 2 entries # Create cache with max 2 entries
cache = SummaryCache(str(cache_file), max_entries=2) cache = SummaryCache(str(cache_file), max_entries=2)
@@ -64,9 +58,6 @@ def test_summary_cache_lru():
assert cache.get_summary("file3.py", "hash3") is None assert cache.get_summary("file3.py", "hash3") is None
assert cache.get_summary("file2.py", "hash2") == "summary2" assert cache.get_summary("file2.py", "hash2") == "summary2"
assert cache.get_summary("file4.py", "hash4") == "summary4" assert cache.get_summary("file4.py", "hash4") == "summary4"
if cache_dir.exists():
shutil.rmtree(cache_dir)
if __name__ == "__main__": if __name__ == "__main__":
test_get_file_hash() test_get_file_hash()