"""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", "generate_per_subq", "filter_intro", "filter_section", "filter_outro", } assert "{question}" in prompts["decompose"] assert "{question}" in prompts["filter"] assert "{question}" in prompts["generate"] assert "{context}" in prompts["generate"] assert "{context_sections}" in prompts["generate_per_subq"] 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