legco_ai_assistant/backend/app/test/test_phase3_prompts_router.py

217 lines
7.2 KiB
Python

"""Tests for Package 3.2 prompts router — HTTP endpoint integration tests.
Uses real sqlite3 with tmp_path. TestClient hits a minimal FastAPI app
wired with the prompts router. No mocks on the DB layer.
"""
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from app.core.sqlite_db import init_prompts_db, seed_default_profiles, _get_db
from app.routers.prompts import router
# ── Fixture ────────────────────────────────────────────────────────────────
@pytest.fixture
def client(tmp_path, monkeypatch):
prompts_path = str(tmp_path / "prompts.db")
monkeypatch.setenv("PROMPTS_DB_PATH", prompts_path)
monkeypatch.setenv("HISTORY_DB_PATH", str(tmp_path / "history.db"))
from app.core.config import get_settings
get_settings.cache_clear()
from app.core.dependencies import get_settings_cached
get_settings_cached.cache_clear()
conn = _get_db(prompts_path)
init_prompts_db(conn)
seed_default_profiles(conn)
conn.close()
test_app = FastAPI()
test_app.include_router(router)
yield TestClient(test_app)
get_settings_cached.cache_clear()
get_settings.cache_clear()
# ── GET /profiles ──────────────────────────────────────────────────────────
def test_get_profiles_returns_200_with_three_items(client):
resp = client.get("/api/v1/prompts/profiles")
assert resp.status_code == 200
data = resp.json()
assert len(data["profiles"]) == 3
names = [p["name"] for p in data["profiles"]]
assert names == ["A", "B", "C"]
active_map = {p["name"]: p["is_active"] for p in data["profiles"]}
assert active_map["A"] is True
assert active_map["B"] is False
assert active_map["C"] is False
# ── GET /profiles/{name} ──────────────────────────────────────────────────
def test_get_profile_prompts_a_returns_200(client):
resp = client.get("/api/v1/prompts/profiles/A")
assert resp.status_code == 200
data = resp.json()
assert data["profile_name"] == "A"
assert set(data["prompts"].keys()) == {"decompose", "filter", "generate"}
def test_get_profile_prompts_invalid_returns_400(client):
resp = client.get("/api/v1/prompts/profiles/D")
assert resp.status_code == 400
# ── PUT /profiles/{name}/activate ─────────────────────────────────────────
def test_activate_profile_b_then_get_confirms(client):
resp = client.put("/api/v1/prompts/profiles/B/activate")
assert resp.status_code == 200
assert resp.json()["active_profile"] == "B"
resp = client.get("/api/v1/prompts/profiles")
active_map = {p["name"]: p["is_active"] for p in resp.json()["profiles"]}
assert active_map["B"] is True
assert active_map["A"] is False
assert active_map["C"] is False
# ── PUT /profiles/{name}/{step} ───────────────────────────────────────────
def test_update_prompt_returns_200_and_persists(client):
new_template = "Updated decompose for {question}"
resp = client.put(
"/api/v1/prompts/profiles/A/decompose",
json={"template": new_template},
)
assert resp.status_code == 200
assert resp.json()["step"] == "decompose"
resp = client.get("/api/v1/prompts/profiles/A")
assert resp.json()["prompts"]["decompose"] == new_template
# ── PUT /profiles/{name}/all ──────────────────────────────────────────────
def test_update_all_prompts_batch_returns_200_and_persists(client):
new_prompts = {
"decompose": "Batch decompose",
"filter": "Batch filter",
"generate": "Batch generate",
}
resp = client.put(
"/api/v1/prompts/profiles/A/all",
json={"prompts": new_prompts},
)
assert resp.status_code == 200
assert resp.json()["profile"] == "A"
resp = client.get("/api/v1/prompts/profiles/A")
prompts = resp.json()["prompts"]
assert prompts["decompose"] == "Batch decompose"
assert prompts["filter"] == "Batch filter"
assert prompts["generate"] == "Batch generate"
# ── PUT /profiles/{name}/reset ────────────────────────────────────────────
def test_reset_single_step_returns_200(client):
from app.core.sqlite_db import _SEED_TEMPLATES
client.put(
"/api/v1/prompts/profiles/A/decompose",
json={"template": "MODIFIED"},
)
resp = client.put(
"/api/v1/prompts/profiles/A/reset",
json={"step": "decompose"},
)
assert resp.status_code == 200
assert resp.json()["reset_step"] == "decompose"
resp = client.get("/api/v1/prompts/profiles/A")
assert resp.json()["prompts"]["decompose"] == _SEED_TEMPLATES["decompose"]
def test_reset_all_steps_returns_200(client):
from app.core.sqlite_db import _SEED_TEMPLATES
client.put("/api/v1/prompts/profiles/A/all", json={"prompts": {
"decompose": "MODIFIED decompose",
"filter": "MODIFIED filter",
"generate": "MODIFIED generate",
}})
resp = client.put(
"/api/v1/prompts/profiles/A/reset",
json={"step": None},
)
assert resp.status_code == 200
assert resp.json()["reset_step"] == "all"
resp = client.get("/api/v1/prompts/profiles/A")
prompts = resp.json()["prompts"]
assert prompts["decompose"] == _SEED_TEMPLATES["decompose"]
assert prompts["filter"] == _SEED_TEMPLATES["filter"]
assert prompts["generate"] == _SEED_TEMPLATES["generate"]
def test_reset_with_no_body_resets_all(client):
from app.core.sqlite_db import _SEED_TEMPLATES
client.put("/api/v1/prompts/profiles/A/all", json={"prompts": {
"decompose": "MODIFIED",
"filter": "MODIFIED",
"generate": "MODIFIED",
}})
resp = client.put("/api/v1/prompts/profiles/A/reset")
assert resp.status_code == 200
assert resp.json()["reset_step"] == "all"
resp = client.get("/api/v1/prompts/profiles/A")
prompts = resp.json()["prompts"]
assert prompts["decompose"] == _SEED_TEMPLATES["decompose"]
assert prompts["filter"] == _SEED_TEMPLATES["filter"]
assert prompts["generate"] == _SEED_TEMPLATES["generate"]
# ── Validation: invalid name and step ──────────────────────────────────────
def test_invalid_profile_name_returns_400(client):
resp = client.get("/api/v1/prompts/profiles/D")
assert resp.status_code == 400
resp = client.put("/api/v1/prompts/profiles/D/activate")
assert resp.status_code == 400
def test_invalid_step_returns_400(client):
resp = client.put(
"/api/v1/prompts/profiles/A/nonexistent",
json={"template": "test"},
)
assert resp.status_code == 400