checkpoint(Saved system prompt presets)
This commit is contained in:
@@ -200,14 +200,14 @@ def live_gui() -> Generator[tuple[subprocess.Popen, str], None, None]:
|
||||
temp_workspace.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Create minimal project files to avoid cluttering root
|
||||
# NOTE: Do NOT create config.toml here - we use SLOP_CONFIG env var
|
||||
# to point to the actual project root config.toml
|
||||
(temp_workspace / "manual_slop.toml").write_text("[project]\nname = 'TestProject'\n", encoding="utf-8")
|
||||
(temp_workspace / "conductor" / "tracks").mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Resolve absolute paths for shared resources
|
||||
project_root = Path(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
config_file = project_root / "config.toml"
|
||||
config_file = temp_workspace / "config.toml"
|
||||
if not config_file.exists():
|
||||
config_file = project_root / "config.toml"
|
||||
cred_file = project_root / "credentials.toml"
|
||||
mcp_file = project_root / "mcp_env.toml"
|
||||
|
||||
@@ -249,6 +249,7 @@ def live_gui() -> Generator[tuple[subprocess.Popen, str], None, None]:
|
||||
env["SLOP_CREDENTIALS"] = str(cred_file.absolute())
|
||||
if mcp_file.exists():
|
||||
env["SLOP_MCP_ENV"] = str(mcp_file.absolute())
|
||||
env["SLOP_GLOBAL_PRESETS"] = str((temp_workspace / "presets.toml").absolute())
|
||||
|
||||
process = subprocess.Popen(
|
||||
["uv", "run", "python", "-u", gui_script, "--enable-test-hooks"],
|
||||
|
||||
134
tests/test_preset_manager.py
Normal file
134
tests/test_preset_manager.py
Normal file
@@ -0,0 +1,134 @@
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from src.presets import PresetManager
|
||||
from src.models import Preset
|
||||
|
||||
def test_load_all_merged(tmp_path, monkeypatch):
|
||||
"""Tests that load_all correctly merges global and project presets."""
|
||||
global_file = tmp_path / "global_presets.toml"
|
||||
project_root = tmp_path / "project"
|
||||
project_root.mkdir()
|
||||
project_file = project_root / "project_presets.toml"
|
||||
|
||||
# Setup global presets
|
||||
global_file.write_text("""
|
||||
[presets.global_only]
|
||||
system_prompt = "global prompt"
|
||||
temperature = 0.5
|
||||
|
||||
[presets.override_me]
|
||||
system_prompt = "original prompt"
|
||||
""", encoding="utf-8")
|
||||
|
||||
# Setup project presets
|
||||
project_file.write_text("""
|
||||
[presets.project_only]
|
||||
system_prompt = "project prompt"
|
||||
max_output_tokens = 100
|
||||
|
||||
[presets.override_me]
|
||||
system_prompt = "overridden prompt"
|
||||
""", encoding="utf-8")
|
||||
|
||||
monkeypatch.setattr("src.presets.get_global_presets_path", lambda: global_file)
|
||||
monkeypatch.setattr("src.presets.get_project_presets_path", lambda p: project_file)
|
||||
|
||||
pm = PresetManager(project_root=project_root)
|
||||
presets = pm.load_all()
|
||||
|
||||
assert len(presets) == 3
|
||||
assert presets["global_only"].system_prompt == "global prompt"
|
||||
assert presets["global_only"].temperature == 0.5
|
||||
assert presets["project_only"].system_prompt == "project prompt"
|
||||
assert presets["project_only"].max_output_tokens == 100
|
||||
assert presets["override_me"].system_prompt == "overridden prompt"
|
||||
|
||||
def test_save_preset_global(tmp_path, monkeypatch):
|
||||
"""Tests saving a preset to the global scope."""
|
||||
global_file = tmp_path / "global_presets.toml"
|
||||
monkeypatch.setattr("src.presets.get_global_presets_path", lambda: global_file)
|
||||
|
||||
pm = PresetManager()
|
||||
preset = Preset(name="new_global", system_prompt="new global prompt", temperature=0.7)
|
||||
pm.save_preset(preset, scope="global")
|
||||
|
||||
assert global_file.exists()
|
||||
loaded_presets = pm.load_all()
|
||||
assert "new_global" in loaded_presets
|
||||
assert loaded_presets["new_global"].system_prompt == "new global prompt"
|
||||
assert loaded_presets["new_global"].temperature == 0.7
|
||||
|
||||
def test_save_preset_project(tmp_path, monkeypatch):
|
||||
"""Tests saving a preset to the project scope."""
|
||||
project_root = tmp_path / "project"
|
||||
project_root.mkdir()
|
||||
project_file = project_root / "project_presets.toml"
|
||||
global_file = tmp_path / "global_presets.toml"
|
||||
|
||||
monkeypatch.setattr("src.presets.get_global_presets_path", lambda: global_file)
|
||||
monkeypatch.setattr("src.presets.get_project_presets_path", lambda p: project_file)
|
||||
|
||||
pm = PresetManager(project_root=project_root)
|
||||
preset = Preset(name="new_project", system_prompt="new project prompt", max_output_tokens=500)
|
||||
pm.save_preset(preset, scope="project")
|
||||
|
||||
assert project_file.exists()
|
||||
# Global file should NOT have been created/modified
|
||||
assert not global_file.exists()
|
||||
|
||||
loaded_presets = pm.load_all()
|
||||
assert "new_project" in loaded_presets
|
||||
assert loaded_presets["new_project"].system_prompt == "new project prompt"
|
||||
assert loaded_presets["new_project"].max_output_tokens == 500
|
||||
|
||||
def test_save_preset_project_no_root():
|
||||
"""Tests that saving to project scope fails if no project root is provided."""
|
||||
pm = PresetManager(project_root=None)
|
||||
preset = Preset(name="fail", system_prompt="fail")
|
||||
with pytest.raises(ValueError, match="Project scope requested but no project_root provided"):
|
||||
pm.save_preset(preset, scope="project")
|
||||
|
||||
def test_delete_preset(tmp_path, monkeypatch):
|
||||
"""Tests deleting a preset from both scopes."""
|
||||
global_file = tmp_path / "global_presets.toml"
|
||||
project_root = tmp_path / "project"
|
||||
project_root.mkdir()
|
||||
project_file = project_root / "project_presets.toml"
|
||||
|
||||
global_file.write_text("""
|
||||
[presets.global1]
|
||||
system_prompt = "g1"
|
||||
[presets.both]
|
||||
system_prompt = "both_g"
|
||||
""", encoding="utf-8")
|
||||
|
||||
project_file.write_text("""
|
||||
[presets.project1]
|
||||
system_prompt = "p1"
|
||||
[presets.both]
|
||||
system_prompt = "both_p"
|
||||
""", encoding="utf-8")
|
||||
|
||||
monkeypatch.setattr("src.presets.get_global_presets_path", lambda: global_file)
|
||||
monkeypatch.setattr("src.presets.get_project_presets_path", lambda p: project_file)
|
||||
|
||||
pm = PresetManager(project_root=project_root)
|
||||
|
||||
# Delete from project
|
||||
pm.delete_preset("both", scope="project")
|
||||
presets = pm.load_all()
|
||||
assert "project1" in presets
|
||||
# "both" should now show the global version because project override is gone
|
||||
assert presets["both"].system_prompt == "both_g"
|
||||
|
||||
# Delete from global
|
||||
pm.delete_preset("global1", scope="global")
|
||||
presets = pm.load_all()
|
||||
assert "global1" not in presets
|
||||
assert "both" in presets
|
||||
|
||||
# Delete last project preset
|
||||
pm.delete_preset("project1", scope="project")
|
||||
presets = pm.load_all()
|
||||
assert "project1" not in presets
|
||||
assert "both" in presets # still in global
|
||||
77
tests/test_presets.py
Normal file
77
tests/test_presets.py
Normal file
@@ -0,0 +1,77 @@
|
||||
import os
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import shutil
|
||||
from src.presets import PresetManager
|
||||
from src.models import Preset
|
||||
|
||||
class TestPresetManager(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.test_dir = Path(tempfile.mkdtemp())
|
||||
self.project_root = self.test_dir / "project"
|
||||
self.project_root.mkdir()
|
||||
|
||||
# Mocking global path is harder since it's hardcoded in paths.py
|
||||
# But we can at least test project-specific ones and the manager's logic.
|
||||
# For the sake of this test, we will only test what we can without
|
||||
# affecting the real global_presets.toml if possible.
|
||||
|
||||
self.manager = PresetManager(project_root=self.project_root)
|
||||
# Override paths for testing to avoid touching real files
|
||||
self.manager.global_path = self.test_dir / "global_presets.toml"
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.test_dir)
|
||||
|
||||
def test_save_and_load_global(self):
|
||||
preset = Preset(name="test_global", system_prompt="You are a global assistant")
|
||||
self.manager.save_preset(preset, scope="global")
|
||||
|
||||
presets = self.manager.load_all()
|
||||
self.assertIn("test_global", presets)
|
||||
self.assertEqual(presets["test_global"].system_prompt, "You are a global assistant")
|
||||
|
||||
def test_save_and_load_project(self):
|
||||
preset = Preset(name="test_project", system_prompt="You are a project assistant")
|
||||
self.manager.save_preset(preset, scope="project")
|
||||
|
||||
presets = self.manager.load_all()
|
||||
self.assertIn("test_project", presets)
|
||||
self.assertEqual(presets["test_project"].system_prompt, "You are a project assistant")
|
||||
|
||||
def test_project_overwrites_global(self):
|
||||
g_preset = Preset(name="shared", system_prompt="Global version")
|
||||
p_preset = Preset(name="shared", system_prompt="Project version")
|
||||
|
||||
self.manager.save_preset(g_preset, scope="global")
|
||||
self.manager.save_preset(p_preset, scope="project")
|
||||
|
||||
presets = self.manager.load_all()
|
||||
self.assertEqual(presets["shared"].system_prompt, "Project version")
|
||||
|
||||
def test_delete_preset(self):
|
||||
preset = Preset(name="to_delete", system_prompt="Delete me")
|
||||
self.manager.save_preset(preset, scope="project")
|
||||
|
||||
presets = self.manager.load_all()
|
||||
self.assertIn("to_delete", presets)
|
||||
|
||||
self.manager.delete_preset("to_delete", scope="project")
|
||||
presets = self.manager.load_all()
|
||||
self.assertNotIn("to_delete", presets)
|
||||
|
||||
def test_dynamic_project_path(self):
|
||||
"""Verifies that project_path updates when project_root changes."""
|
||||
initial_root = self.test_dir / "project1"
|
||||
initial_root.mkdir()
|
||||
manager = PresetManager(project_root=initial_root)
|
||||
self.assertEqual(manager.project_path, initial_root / "project_presets.toml")
|
||||
|
||||
new_root = self.test_dir / "project2"
|
||||
new_root.mkdir()
|
||||
manager.project_root = new_root
|
||||
self.assertEqual(manager.project_path, new_root / "project_presets.toml")
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
103
tests/test_saved_presets_sim.py
Normal file
103
tests/test_saved_presets_sim.py
Normal file
@@ -0,0 +1,103 @@
|
||||
import pytest
|
||||
import time
|
||||
import tomli_w
|
||||
import os
|
||||
import json
|
||||
from pathlib import Path
|
||||
from src.api_hook_client import ApiHookClient
|
||||
|
||||
def test_preset_switching(live_gui):
|
||||
client = ApiHookClient()
|
||||
|
||||
# Paths for presets
|
||||
temp_workspace = Path("tests/artifacts/live_gui_workspace")
|
||||
global_presets_path = temp_workspace / "presets.toml"
|
||||
project_presets_path = temp_workspace / "project_presets.toml"
|
||||
manual_slop_path = temp_workspace / "manual_slop.toml"
|
||||
|
||||
# Cleanup before test
|
||||
if global_presets_path.exists(): global_presets_path.unlink()
|
||||
if project_presets_path.exists(): project_presets_path.unlink()
|
||||
|
||||
try:
|
||||
# Create a global preset
|
||||
global_presets_path.write_text(tomli_w.dumps({
|
||||
"presets": {
|
||||
"TestGlobal": {
|
||||
"system_prompt": "Global Prompt",
|
||||
"temperature": 0.7
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
# Create a project preset
|
||||
project_presets_path.write_text(tomli_w.dumps({
|
||||
"presets": {
|
||||
"TestProject": {
|
||||
"system_prompt": "Project Prompt",
|
||||
"temperature": 0.3
|
||||
},
|
||||
"TestGlobal": { # Override
|
||||
"system_prompt": "Overridden Prompt",
|
||||
"temperature": 0.5
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
# Switch to the local project to ensure context is correct
|
||||
client.push_event("custom_callback", {
|
||||
"callback": "_switch_project",
|
||||
"args": [str(manual_slop_path.absolute())]
|
||||
})
|
||||
time.sleep(2)
|
||||
|
||||
# Trigger reload of presets (just in case)
|
||||
client.push_event("custom_callback", {
|
||||
"callback": "_refresh_from_project",
|
||||
"args": []
|
||||
})
|
||||
time.sleep(2) # Wait for processing
|
||||
|
||||
# Verify where it thinks the project is
|
||||
state = client.get_gui_state()
|
||||
print(f"DEBUG: ui_files_base_dir={state.get('files_base_dir')}")
|
||||
|
||||
# Apply Global Preset (should use override from project if available in merged list)
|
||||
client.push_event("custom_callback", {
|
||||
"callback": "_apply_preset",
|
||||
"args": ["TestGlobal", "global"]
|
||||
})
|
||||
time.sleep(1)
|
||||
|
||||
# Verify state
|
||||
state = client.get_gui_state()
|
||||
assert state["global_preset_name"] == "TestGlobal"
|
||||
assert state["global_system_prompt"] == "Overridden Prompt"
|
||||
assert state["temperature"] == 0.5
|
||||
|
||||
# Apply Project Preset
|
||||
client.push_event("custom_callback", {
|
||||
"callback": "_apply_preset",
|
||||
"args": ["TestProject", "project"]
|
||||
})
|
||||
time.sleep(1)
|
||||
|
||||
state = client.get_gui_state()
|
||||
assert state["project_preset_name"] == "TestProject"
|
||||
assert state["project_system_prompt"] == "Project Prompt"
|
||||
assert state["temperature"] == 0.3
|
||||
# Select "None"
|
||||
client.push_event("custom_callback", {
|
||||
"callback": "_apply_preset",
|
||||
"args": ["None", "global"]
|
||||
})
|
||||
time.sleep(1)
|
||||
state = client.get_gui_state()
|
||||
assert not state.get("global_preset_name") # Should be None or ""
|
||||
|
||||
# Prompt remains from previous application
|
||||
assert state["global_system_prompt"] == "Overridden Prompt"
|
||||
finally:
|
||||
# Cleanup
|
||||
if global_presets_path.exists(): global_presets_path.unlink()
|
||||
if project_presets_path.exists(): project_presets_path.unlink()
|
||||
Reference in New Issue
Block a user