legco_ai_assistant/backend/app/test/test_phase3_prompt_service.py

249 lines
8.7 KiB
Python

"""Tests for Package 3.2 PromptService — CRUD for prompt profiles and templates.
Uses real sqlite3 with tmp_path for full isolation. No mocks.
Each test gets its own fresh database seeded with A/B/C profiles.
"""
import sqlite3
import pytest
from app.core.sqlite_db import _SEED_TEMPLATES, init_prompts_db, seed_default_profiles
from app.services.prompt_service import PromptService
# ── Helper ─────────────────────────────────────────────────────────────────
def _create_service(tmp_path) -> PromptService:
db_path = str(tmp_path / "test.db")
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA foreign_keys=ON")
init_prompts_db(conn)
seed_default_profiles(conn)
conn.close()
return PromptService(db_path=db_path)
# ── list_profiles ──────────────────────────────────────────────────────────
def test_list_profiles_returns_abc_with_a_active(tmp_path):
svc = _create_service(tmp_path)
profiles = svc.list_profiles()
assert len(profiles) == 3
names = [p["name"] for p in profiles]
assert names == ["A", "B", "C"]
active_map = {p["name"]: p["is_active"] for p in profiles}
assert active_map["A"] is True
assert active_map["B"] is False
assert active_map["C"] is False
# ── activate_profile ───────────────────────────────────────────────────────
def test_activate_profile_b(tmp_path):
svc = _create_service(tmp_path)
svc.activate_profile("B")
profiles = svc.list_profiles()
active_map = {p["name"]: p["is_active"] for p in profiles}
assert active_map["A"] is False
assert active_map["B"] is True
assert active_map["C"] is False
def test_activate_profile_c(tmp_path):
svc = _create_service(tmp_path)
svc.activate_profile("C")
profiles = svc.list_profiles()
active_map = {p["name"]: p["is_active"] for p in profiles}
assert active_map["A"] is False
assert active_map["B"] is False
assert active_map["C"] is True
def test_activate_profile_invalid_name_raises(tmp_path):
svc = _create_service(tmp_path)
with pytest.raises(ValueError, match="Invalid profile name"):
svc.activate_profile("D")
# ── get_active_profile_name ────────────────────────────────────────────────
def test_get_active_profile_name_returns_a_after_seed(tmp_path):
svc = _create_service(tmp_path)
assert svc.get_active_profile_name() == "A"
def test_get_active_profile_name_after_switch(tmp_path):
svc = _create_service(tmp_path)
svc.activate_profile("B")
assert svc.get_active_profile_name() == "B"
# ── get_profile_prompts ───────────────────────────────────────────────────
def test_get_profile_prompts_returns_all_three_steps(tmp_path):
svc = _create_service(tmp_path)
prompts = svc.get_profile_prompts("A")
assert set(prompts.keys()) == {"decompose", "filter", "generate"}
assert "{question}" in prompts["decompose"]
assert "{question}" in prompts["filter"]
assert "{context_sections}" in prompts["generate"]
def test_get_profile_prompts_invalid_name_raises(tmp_path):
svc = _create_service(tmp_path)
with pytest.raises(ValueError, match="Invalid profile name"):
svc.get_profile_prompts("D")
# ── get_prompt_template ────────────────────────────────────────────────────
def test_get_prompt_template_for_active_profile(tmp_path):
svc = _create_service(tmp_path)
template = svc.get_prompt_template("decompose")
assert template == _SEED_TEMPLATES["decompose"]
def test_get_prompt_template_after_activate(tmp_path):
svc = _create_service(tmp_path)
svc.update_prompt("B", "decompose", "B custom template")
svc.activate_profile("B")
assert svc.get_prompt_template("decompose") == "B custom template"
def test_get_prompt_template_invalid_step_raises(tmp_path):
svc = _create_service(tmp_path)
with pytest.raises(ValueError, match="Invalid step"):
svc.get_prompt_template("nonexistent")
# ── update_prompt ──────────────────────────────────────────────────────────
def test_update_prompt_persists_change(tmp_path):
svc = _create_service(tmp_path)
new_template = "Custom decompose prompt for {question}"
svc.update_prompt("A", "decompose", new_template)
prompts = svc.get_profile_prompts("A")
assert prompts["decompose"] == new_template
assert prompts["filter"] == _SEED_TEMPLATES["filter"]
assert prompts["generate"] == _SEED_TEMPLATES["generate"]
def test_update_prompt_invalid_name_raises(tmp_path):
svc = _create_service(tmp_path)
with pytest.raises(ValueError, match="Invalid profile name"):
svc.update_prompt("D", "decompose", "template")
def test_update_prompt_invalid_step_raises(tmp_path):
svc = _create_service(tmp_path)
with pytest.raises(ValueError, match="Invalid step"):
svc.update_prompt("A", "nonexistent", "template")
# ── update_all_prompts ─────────────────────────────────────────────────────
def test_update_all_prompts_batch(tmp_path):
svc = _create_service(tmp_path)
new_prompts = {
"decompose": "New decompose",
"filter": "New filter",
"generate": "New generate",
}
svc.update_all_prompts("A", new_prompts)
prompts = svc.get_profile_prompts("A")
assert prompts["decompose"] == "New decompose"
assert prompts["filter"] == "New filter"
assert prompts["generate"] == "New generate"
def test_update_all_prompts_invalid_name_raises(tmp_path):
svc = _create_service(tmp_path)
with pytest.raises(ValueError, match="Invalid profile name"):
svc.update_all_prompts("D", {"decompose": "x"})
def test_update_all_prompts_invalid_step_raises(tmp_path):
svc = _create_service(tmp_path)
with pytest.raises(ValueError, match="Invalid step"):
svc.update_all_prompts("A", {"nonexistent": "x"})
# ── reset_to_defaults ─────────────────────────────────────────────────────
def test_reset_to_defaults_all_steps(tmp_path):
svc = _create_service(tmp_path)
svc.update_all_prompts("A", {
"decompose": "MODIFIED decompose",
"filter": "MODIFIED filter",
"generate": "MODIFIED generate",
})
svc.reset_to_defaults("A", step=None)
prompts = svc.get_profile_prompts("A")
assert prompts["decompose"] == _SEED_TEMPLATES["decompose"]
assert prompts["filter"] == _SEED_TEMPLATES["filter"]
assert prompts["generate"] == _SEED_TEMPLATES["generate"]
def test_reset_to_defaults_single_step(tmp_path):
svc = _create_service(tmp_path)
svc.update_all_prompts("A", {
"decompose": "MODIFIED decompose",
"filter": "MODIFIED filter",
"generate": "MODIFIED generate",
})
svc.reset_to_defaults("A", step="filter")
prompts = svc.get_profile_prompts("A")
assert prompts["decompose"] == "MODIFIED decompose"
assert prompts["filter"] == _SEED_TEMPLATES["filter"]
assert prompts["generate"] == "MODIFIED generate"
def test_reset_to_defaults_invalid_name_raises(tmp_path):
svc = _create_service(tmp_path)
with pytest.raises(ValueError, match="Invalid profile name"):
svc.reset_to_defaults("D")
# ── Edge cases ─────────────────────────────────────────────────────────────
def test_empty_string_template_allowed(tmp_path):
svc = _create_service(tmp_path)
svc.update_prompt("A", "decompose", "")
prompts = svc.get_profile_prompts("A")
assert prompts["decompose"] == ""
def test_very_long_template_allowed(tmp_path):
svc = _create_service(tmp_path)
long_template = "x" * 50_000
svc.update_prompt("A", "decompose", long_template)
prompts = svc.get_profile_prompts("A")
assert prompts["decompose"] == long_template
assert len(prompts["decompose"]) == 50_000