test: add tests for setup wizard personalization and system prompt builder

Cover _USE_CASE_PLUGINS mapping, _suggest_plugins side effects, _build_system_base
output for all name/purpose combinations, and GeneralConfig.purpose round-trip.
Also update CLAUDE.md with the testing workflow rule.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
curo1305
2026-05-19 10:43:20 +02:00
parent ace9561c87
commit 9735a5559e
4 changed files with 177 additions and 0 deletions
+8
View File
@@ -261,6 +261,14 @@ chore: description
## Workflow Rules
### Testing
- **Write tests for every new feature.** A feature without tests is incomplete — do not commit without them.
- New tests go in `tests/unit/` for pure-logic helpers and `tests/security/` for security-boundary code.
- All existing tests must continue to pass — run `pytest tests/ -v` before committing.
- Test pure functions directly; do not test interactive I/O (questionary, Rich output) — only test the logic helpers those flows call.
- For Rich output, capture side effects by monkeypatching `console.print` rather than using `capsys`.
### Bugfixes
- **Stay under 50 lines changed.** Find the root cause and fix it directly.
+67
View File
@@ -88,3 +88,70 @@ def test_trim_to_budget_no_trim_when_under_budget():
def test_trim_to_budget_empty_list():
from pyra.chat.history import _trim_to_budget
assert _trim_to_budget([], 1000) == []
# ── _build_system_base tests ───────────────────────────────────────────────────
def test_build_system_base_default_identity():
from pyra.chat.history import _build_system_base
result = _build_system_base("User", "Pyra", "")
assert "You are Pyra" in result
assert "User" in result
def test_build_system_base_custom_name_and_assistant():
from pyra.chat.history import _build_system_base
result = _build_system_base("Alice", "Aria", "")
assert "You are Aria" in result
assert "Alice" in result
def test_build_system_base_no_purpose_omits_focus_block():
from pyra.chat.history import _build_system_base
result = _build_system_base("User", "Pyra", "")
assert "primary purpose" not in result
assert "not a general-purpose chatbot" not in result
def test_build_system_base_with_purpose_includes_focus_block():
from pyra.chat.history import _build_system_base
result = _build_system_base("User", "Pyra", "manage my Nextcloud server")
assert "primary purpose" in result
assert "manage my Nextcloud server" in result
assert "not a general-purpose chatbot" in result
def test_build_system_base_always_includes_security_constraints():
from pyra.chat.history import _build_system_base
for purpose in ("", "manage servers"):
result = _build_system_base("User", "Pyra", purpose)
assert "vault" in result
assert "shell commands" in result
def test_build_for_api_uses_config_user_name(tmp_pyra_home, monkeypatch):
monkeypatch.setattr("pyra.chat.history.load_context_for_session", lambda: "")
from pyra.config.schema import PyraConfig, ProviderConfig, GeneralConfig
from pyra.chat.history import ConversationHistory
cfg = PyraConfig(
ai=ProviderConfig(provider_id="lmstudio", model="gemma"),
general=GeneralConfig(user_name="Alice", assistant_name="Aria", purpose=""),
)
h = ConversationHistory(cfg)
system = h.build_for_api()[0]["content"]
assert "Alice" in system
assert "Aria" in system
def test_build_for_api_uses_purpose(tmp_pyra_home, monkeypatch):
monkeypatch.setattr("pyra.chat.history.load_context_for_session", lambda: "")
from pyra.config.schema import PyraConfig, ProviderConfig, GeneralConfig
from pyra.chat.history import ConversationHistory
cfg = PyraConfig(
ai=ProviderConfig(provider_id="lmstudio", model="gemma"),
general=GeneralConfig(purpose="manage my home server"),
)
h = ConversationHistory(cfg)
system = h.build_for_api()[0]["content"]
assert "manage my home server" in system
assert "primary purpose" in system
+10
View File
@@ -55,6 +55,14 @@ def test_general_config_defaults():
g = GeneralConfig()
assert g.user_name == "User"
assert g.assistant_name == "Pyra"
assert g.purpose == ""
def test_general_config_purpose_roundtrip():
from pyra.config.schema import GeneralConfig
cfg = GeneralConfig(user_name="Alice", purpose="manage servers")
assert cfg.user_name == "Alice"
assert cfg.purpose == "manage servers"
def test_pyraconfig_has_general_and_plugin_settings():
@@ -70,11 +78,13 @@ def test_config_round_trip_preserves_general(tmp_pyra_home):
cfg = PyraConfig(ai=ProviderConfig(provider_id="ollama", model="llama3"))
cfg.general.user_name = "Alice"
cfg.general.assistant_name = "Aria"
cfg.general.purpose = "manage my home server"
save_config(cfg)
loaded = load_config()
assert loaded.general.user_name == "Alice"
assert loaded.general.assistant_name == "Aria"
assert loaded.general.purpose == "manage my home server"
def test_config_round_trip_preserves_plugin_settings(tmp_pyra_home):
+92
View File
@@ -0,0 +1,92 @@
"""Tests for setup wizard personalization helpers."""
import pytest
def test_use_case_plugin_mapping_all_categories_have_entries():
from pyra.setup.wizard import _USE_CASE_PLUGINS
assert all(len(v) > 0 for v in _USE_CASE_PLUGINS.values())
def test_use_case_plugin_mapping_has_expected_categories():
from pyra.setup.wizard import _USE_CASE_PLUGINS
assert "Email" in _USE_CASE_PLUGINS
assert "Development & servers" in _USE_CASE_PLUGINS
assert "Research & web" in _USE_CASE_PLUGINS
def test_use_case_email_contains_email_plugin():
from pyra.setup.wizard import _USE_CASE_PLUGINS
assert "email" in _USE_CASE_PLUGINS["Email"]
def test_use_case_dev_contains_ssh_and_docker():
from pyra.setup.wizard import _USE_CASE_PLUGINS
assert "ssh_tool" in _USE_CASE_PLUGINS["Development & servers"]
assert "docker_tool" in _USE_CASE_PLUGINS["Development & servers"]
def test_use_case_file_management_contains_cloud_stores():
from pyra.setup.wizard import _USE_CASE_PLUGINS
plugins = _USE_CASE_PLUGINS["File management"]
assert "gdrive" in plugins
assert "onedrive" in plugins
assert "dropbox_tool" in plugins
def test_suggest_plugins_empty_use_cases_returns_early(monkeypatch):
calls = []
import pyra.setup.wizard as wiz
monkeypatch.setattr(wiz.console, "print", lambda *a, **kw: calls.append(a))
wiz._suggest_plugins([])
assert calls == []
def test_suggest_plugins_unknown_use_case_returns_early(monkeypatch):
calls = []
import pyra.setup.wizard as wiz
monkeypatch.setattr(wiz.console, "print", lambda *a, **kw: calls.append(a))
wiz._suggest_plugins(["Not a real category"])
assert calls == []
def test_suggest_plugins_valid_use_case_calls_print(monkeypatch):
calls = []
import pyra.setup.wizard as wiz
monkeypatch.setattr(wiz.console, "print", lambda *a, **kw: calls.append(str(a)))
wiz._suggest_plugins(["Email"])
assert len(calls) > 0
def test_suggest_plugins_panel_text_contains_plugin_name(monkeypatch):
from rich.panel import Panel
panels = []
import pyra.setup.wizard as wiz
def capture_print(*args, **kwargs):
for a in args:
if isinstance(a, Panel):
panels.append(a.renderable)
monkeypatch.setattr(wiz.console, "print", capture_print)
wiz._suggest_plugins(["Email"])
assert any("email" in str(p) for p in panels)
def test_suggest_plugins_multiple_categories(monkeypatch):
from rich.panel import Panel
panels = []
import pyra.setup.wizard as wiz
def capture_print(*args, **kwargs):
for a in args:
if isinstance(a, Panel):
panels.append(a.renderable)
monkeypatch.setattr(wiz.console, "print", capture_print)
wiz._suggest_plugins(["Email", "Development & servers"])
combined = " ".join(str(p) for p in panels)
assert "email" in combined
assert "ssh_tool" in combined